java 并发编程读书笔记

1.同步容器,同步容器包括Vector和HashTable,是早期jdk的一部分。另一部分是同步包装类,以Collections.synchronizedxxx的工厂方法创建。

2.同步容器虽然是线程安全的,但是对于复合操作,有时你可能需要加上额外的客户端加锁进行保护,即对于使用这些容器的客户端代码,如果存在复合操作,还是可能存在风险。

3.例如check-and-act操作、循环中的元素操作等,如果在客户端代码中没有额外的锁,都会发生意想不到的问题。

4.造成这些的问题都可以通过在客户端加锁来解决,但是这完全阻止了其他线程在加锁期间对整个容器的修改,降低了并发性 。

5.以Vector容器为例,如果在迭代过程当中发现容器被修改,就会抛出ConcurrentModificationException。

6.容器的toString、equals等方法都有可能会间接得调用迭代器,从而导致ConcurrentModificationException。

7.java5通过几种并发容器解决容器并发的问题:同步容器通过对所有的状态进行串行访问,从而实现了他们的线程安全,这样做的坏处是当多个线程共同竞争容器级锁的时候,吞吐量会降低

8.HashMap对应的并发实现时ConcurrentHashMap,当多数操作为读的时候,CopyOnWriteArrayList是list的并发实现,这些接口提供了对一些复合操作的支持,所以不需要客户端代码增加额外的并发也能正确执行。

9.java5中同样增加了Queue和BlockQueue,jdk提供了几种实现:传统的FIFO队列ConcurrentLinkedQueue、具有优先级的PriorityQueue,这些queue并不会阻塞。

10.java5同样提供了BlockQueue,它增加了可阻塞的插入和获取操作,如果队列是空的,获取操作会被阻塞,对于有界队列,如果队列已满,则插入曹祖偶会被阻塞,这一特性对于实现生产者消费者类型的程序非常有用。

11.同步容器在每个操作的时候都持有一个锁。对于容器的一些操作:例如HashMap的get,List的contains操作等可能需要遍历大量数据的操作,并发下的效率可能会降低。以HashMap的get操作为例,如果Map中的key经过hash后没有被散列均匀,极端得被放置在同一个链表当中,get操作就需要遍历整个链表并调用他们的equals操作。在并发下进行这个操作,其他线程就不能操作容器中的元素,而且由于效率较低,等待的时间也会很长,最终导致并发下的低效率。

12.ConcurrentHashMap使用了分离锁的机制来提高HashMap在高并发下访问的效率问题。ConcurrentHashMap提供了不会抛出ConcurrentModificationException的迭代器,这种迭代器提供弱一致性的支持。此外,那些对整个容器进行操作的方法(size、isEmpty)被弱化了,转而保证容器最基础的get、put、remove、containsKey等操作。ConcurrentHashMap还提供了一些标准的复合操作接口,例如缺少即加入、相等便移除、相等便替换等。

13.相比HashTable和synchronizedMap,应该使用ConcurrentHashMap。14.CopyOnWriteArrayList是ArrayList的改进实现,CopyOnWriteArrayList在内部维护一个不可变数组(长度、数组元素不可变,数组本身引用可变),对于所有的读操作,读取这个不可变数组不会出现并发异常,例如并发下迭代CopyOnWriteArrayList,如果同时有线程对该容器进行了修改,不会抛出ConcurrentModificationException,因为迭代器创建的时候就决定了只能使用哪个时刻的数据备份,正因为这个原因,他也不能及时接受到新的数据修改。对于写操作,CopyOnWriteArrayList的做法是复制一个数组,在新的数组上进行修改,然后set回CopyOnWriteArrayList中维护的基础数据引用中。这样导致在写的时候性能的下降。

15.阻塞队列的两个方法put和take是阻塞方式的,如果队列满了执行put,动作会阻塞,如果take时队列是空的,动作也会阻塞。阻塞队列同时提供了offer方法,如果offer动作无法把数据放入队列,会返回一个失败状态。

16.LinkedBlockingQueue和ArrayBlockQueue是FIFO队列,PriorityBlockingQueue是可以按照优先级顺序排序的队列。BlockingQueue的另外一种实现是SynchronousQueue, SynchronousQueue中不存储数据,他只能允许有线程在等待的时候才能put进去数据,当没有线程在消费数据时,put操作会被阻塞。

16.Synchronizer封闭状态,状态决定着线程执行到某一点时是通过还是被迫等待;Synchronizer还提供操控状态的方法,以及高效等待Synchronizer进入期望状态的方法,即在Synchronizer的api级别用提示性强的api提供给用户操作和等待状态的方法,不需要使用类似同步快这种可读性不强的操作(例如阻塞队列的take、put,CountDownLatch的countDown和await等方法);

17.闭锁Latch是一种Synchronizer,他可以延迟线程的状态,直到闭锁达到终止状态。当闭锁达到终止状态前,所有等待闭锁的线程都在等待,而当闭锁达到终止状态后,所有线程都可以执行了。典型的利用场景是多个线程都依赖某个基础数据的初始化,那就可以在初始化时使用闭锁,其他用到该数据的地方等待闭锁,直到初始化完后,释放闭锁,等待的线程就可以继续执行了。

18.FutureTask同样是一种闭锁,他标识一个可以等待返回值的Callable线程。他的get方法的调用会等待线程执行完成或者达到取消状态,你可以在需要获取结果的线程中线执行FutureTask的任务,在需要数据时再从FutureTask对象中调用get方法获取该数据,如果这个数据获取是一个耗时的计算,则可以提供需要数据线程的响应时间。

19.信号量Semaphore用来控制能够同时访问某特定资源的活动的数量,Semaphore管理一个有效的许可集,活动能够获得许可,完成后释放许可。信号量可以用来实现资源池,把信号量的许可数量维护成容器的size,获得一个容器的项减掉一个信号量许可,当容器中为空时,即信号量为0,你要做的事阻塞容器的获取操作,直到容器不为空为止,容器增加一个容器值,信号量随着增加1。使用Semaphore,你可以把任何容器转换成有界的阻塞队列。

20.关卡类似于闭锁,他们都可以阻塞一组线程,等待一个时间的发生,闭锁是多个线程等待一个事件发生,而关卡则是用于每个线程都在等所有线程共同达到一个状态的场景。CyclicBarrier允许你像构造函数传递关卡行为,当成功通过关卡时会执行,但是在阻塞被释放之前是不执行的。关卡通常用于一个这样的场景:一个计算可以并行完成,但是必须完成于一个步骤相关的操作之后才能进入下一步。例如:每个省的税收计算任务都很复杂,我们可以给每个省开一个线程开始计算,然后为这些计算设置一个CyclicBarrier,当每个线程都完成的时候,再进行汇总计算的工作。

21.如何为计算结果建立高速的缓冲?

21.1使用HashMap存储计算结果,每次请求检查缓冲中使用有值,如没有则重新计算,如有则直接返回,但问题是HashMap不是线程安全的,在检查结果和put结果之间存在check-and-act操作,所以容易导致重复计算的问题,需要把整个check-and-act操作都封装到同步块当中。

22.2HashMap的改进版本是使用ConcurrentHashMap,但是同样还是存在check-and-act操作,如果缓存中没有值,会尝试去计算出值再放入缓存,计算的过程比较久的情况下,会导致可能有很多的线程都进行重复的计算,存在比较严重的资源浪费情况。

22.3在上一版本的基础上,可以引入FutureTask这个异步操作的方式来初始化缓存,即当缓存中没有值时,先放入一个FutureTask封装计算的过程,这个封装的步骤肯定是不耗时的,因为真正的计算可以异步执行,然后往缓存中放入FutureTask对象,让后续的请求从FutureTask中获取缓存内容。

22.4但是即使用了上一版本的方法,依然可能在高并发下存在多个线程同时初始化了FutureTask对象的情况,为了避免两个FutureTask同时工作,可以使用ConcurrentHashMap的putIfAbcent方法,达到只有一个FutureTask放入缓存成功的目的,放入成功后再开始异步计算。通过这种方式可以达到高效维护缓存的目的.

java 并发编程读书笔记

时间: 2024-12-15 03:06:51

java 并发编程读书笔记的相关文章

Java并发编程学习笔记(一)线程安全性 1

什么是线程安全性: 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问."共享"意味着变量可以由多个线程同时访问,而"可变"则意味着变量的值在其生命周期内可以发生变化. 一个对象是否需要线程安全的,取决于他是否被多个线程访问.这指的是在程序中访问对象的方式,而不是对象要实现的功能.要使得对象时线程安全的,需要采用同步机制来协同对对象可变状态的访问.如果无法实现协同,那么可能导致数据破坏以及其他不该出现的结果. 如果当多个线程访

Java并发编程学习笔记

Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现Runnable接口并编写run方法,使得该任务可以执行你的命令.   class MyTask implements Runnable {    private String mName;     public MyTask(String name) {    mName = name;   }  

java并发编程实战笔记

1.复合操作 若一个类里有多个属性状态,对每个属性使用atomic类修饰,并且一个属性更新,要在同一原子操作内更新其他所有属性,这样才是线程安全类.需要整体类的状态操作是原子的. 要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量. 判断同步代码块的合理大小,要权衡安全性.简单性和性能. 当执行时间较长的计算或可能无法快速完成的操作(如网络IO.控制台IO)一定不要持有锁. 2.对象的共享 1)可见性 为了确保所有线程都能看到共享变量的最新值,所有执行读操作或写操作的线程都必须在同

Java并发编程学习笔记(一)——线程安全性

1.当多个线程访问某个状态变量并且其中有一个献策灰姑娘执行写入操作时,必须采用同步机制来协同这些线程对变量的访问.Java中的主要同步机制是关键字synchronized,他提供了一种独占的加锁方式. 2.在任何情况下,只有当类中仅包含自己的状态时,线程安全类才是有意义的. 3.当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些献策灰姑娘讲如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 4.无状态对象一定是线程安全的

JAVA并发编程学习笔记------对象的可见性及发布逸出

一.非原子的64位操作: 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性.最低安全性适用于绝大多数变量,但存在一个例外:非volatile类型的64位数值变量(double,long),Java内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile型的long,double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作,当读取一个非volatile类型的long

Java并发编程学习笔记(二)——对象的共享

主要概念:可见性.重排序.失效数据.最低安全性.发布.逸出.线程封闭(Ad-hoc.栈封闭.ThreadLocal类).不变性.Final域.事实不可变对象. 1.在没有同步的情况下,编译器.处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整.在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论. 2.在多线程中使用共享且可变的long和double等类型的变量是不安全的,除非用关键字volatile来声明它们,或者用锁来保护他们. 3.加锁的含义不

JAVA并发编程学习笔记------线程的三种创建方式

创建线程一般有如下几个方式: 1. 通过继承Thread类来创建一个线程: /** * 步骤1:定义一个继承Thread类的子类 * 步骤2:构造子类的一个对象 * 步骤3:启动线程: * */ public class ThreadTest{ public static void main(String[] args) { //构造子类对象 SubThread subThread = new SubThread(); //启动线程 subThread.start(); } } //定义继承Th

JAVA并发编程学习笔记------锁顺序死锁

一.需求描述: 将资金从一个账户转移到另一个账户. 二.程序实现: (1)账户类: public class Account { private long account; public Account(String user, long account) { this.account = account; } public Account() { super(); } public long getAccount() { return account; } public void setAcc

java并发编程实战笔记-线程安全性

什么是线程安全性 线程安全性定义中最核心的概念就是:**正确性**.我们将单线程的正确性近似 定义为"所见即所知",当多个线程访问这个类的时候,始终能表现出正确的行为, 那么这个类就是线程安全类. 当多个线程访问某个类时,不管运行时环境采用什么调度方式或者这些线程将如何 交替运行,并且调用代码时,不需要额外的同步,就可以产生正确的结果.这个类 就是线程安全类. 在线程安全类上执行任何串行或者并行的操作都不会使对象处于无效状态. 可重入代码:就是这段代码,和其他代码不存在共享状态,只包含