《Java并发编程的艺术》读书笔记

1.减小上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。程序员

无所并发编程:多线程竞争锁时,会引发上下文切换,因此多线程处理数据时,能够用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不一样的线程处理不一样段的数据;算法

CAS算法:Java的Atomic包使用CAS算法来更新数据,而不须要加锁;数据库

使用最少线程:避免建立不须要的线程,好比任务不多,可是建立了不少线程来处理,这样会形成大量线程处于等待状态;编程

协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。小程序

2.避免死锁的几个常见方法:数组

避免一个线程同时获取多个锁; 避免一个线程在锁内同时占用多个资源,尽可能保证每一个锁只占用一个资源; 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制; 对于数据库锁,加锁和解锁必须在一个数据库连接里,不然会出现解锁失败的状况。 3.在Java中,锁一共有四种,级别从低到高一次为:无所状态,偏向锁状态,轻量级锁状态和重量级锁状态。这几个状态会随着竞争状况逐渐升级,锁能够升级但不能降级,意味着偏向锁升级为轻量级锁后不能降级为偏向锁。缓存

4.在大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低而引入了偏向锁。当一个线程访问同步代码块获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。若是测试成功,表示线程已经得到了锁。若是测试失败,则须要再测试一下Mark Word中偏向锁的标识是否设置成了1(表示当前是偏向锁):若是没有设置,则使用CAS竞争锁;若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。安全

5.偏向锁使用了一种等到竞争出现才释放锁的机制,因此当其余线程尝试竞争偏向锁时,持有偏向锁的线程才会释放。偏向锁的撤销,须要等待全局安全点(在这个时间点上没有正在执行了字节码)。它会首先暂停拥有偏向锁的线程,而后检查持有偏向锁的线程是否活着,若是线程不处于活动状态,则将对象头设置成无锁状态;若是线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么从新偏向于其余线程,要么恢复到无锁或者标记对象不适合做为偏向锁,最后唤醒暂停的线程。服务器

6.由于自旋会消耗CPU,为了不无用的自旋(好比得到锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其余线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁以后会唤醒这些线程,被唤醒的线程会进行新一轮的夺锁之争。数据结构

7.原子操做意为“不可被中断的一个或一系列操做”。

8.术语定义: 缓存行(Cache line):缓存的最小操做单位; 比较并交换(Compare and Swap, CAS):CAS操做须要输入两个数值,一个旧值(指望操做前的值)和一个新值,在操做期间先比较旧值有没有发生变化,若是没有发生变化,才交换成新值,发生了变化则不交换; CPU流水线(CPU pipeline):CPU流水线的工做方式就像工业生产上的装配流水线,在CPU中由5~6个不一样功能的电路单元组成一条指令处理流水线,而后将一条X86指令分红5~6步后再由这些电路单元分别执行,这样就能实如今一个CPU时钟周期完成一条指令,所以提升CPU的运算速度。 内存顺序冲突(Memory order violation):内存顺序冲突通常是由假共享引发的,假共享是指多个CPU同时修改同一个缓存行的不一样部分而引发其中一个CPU的操做无效,当出现这个内存顺序冲突时,CPU必须清空流水线。

9.处理器实现原子操做的方式:

经过总线锁保证原子性。总线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其余处理器的请求将被阻塞,那么该处理器能够独占共享内存。 经过缓存锁定来保证原子性。频繁使用的内存会缓存再处理器的L一、L2和L3高速缓存里,那么原子操做就能够直接在处理器内部缓存中进行,并不须要声明总线锁。所谓“缓存锁定”是指内存区域若是被缓存再处理器的缓存行中,而且在Lock操做期间被锁定,那么当它执行锁操做会写到内存时,处理器不在总线上声言LOCK #信号,而是修改内部内存地址,并容许它的缓存一致性机制来保证操做的原子性,由于缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当其余处理器回写已被锁定的缓存行的数据时,会使缓存行无效。 10.若是一个操做执行的结果须要对另外一个操做可见,那么两个操做之间必需要存在happens-before关系。

11.happens-before规则以下:

程序顺序规则:一个线程中的每一个操做,happens-before于该线程中的任意后续操做; 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁; volatile变量规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读; 传递性:若是A happens-before B,B happens-before C,那么A happens-before C; 12.happens-before仅仅要求前一个操做(执行的结果)对后一个操做可见,且前一个操做按顺序排在第二个操做以前。

13.重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列而进行从新排序的一种手段。

14.编译器和处理器会对操做进行重排序,编译器和处理在重排序时,会遵照数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操做的执行顺序;这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操做,不一样处理器之间和不一样线程之间的数据依赖性不被编译器和处理器考虑。

15.as-if-serial语义的意思是:无论怎么重排序(编译器和处理器为了提升并行度),(单线程)程序的执行结果不能被改变,编译器,runtime和处理器都必须遵照as-if-serial语义。

16.在计算机中,软件技术和硬件技术有一个共同的目标:在不改变程序执行结果的前提下,尽量提升并行度。

17.当代码中存在控制依赖性时,会影响指令序列执行的并行度。为此,编译器和处理器会采用猜想执行来客户控制相关性对并行度的影响。

18.在单线程程序中,对存在控制依赖的程序重排序,不会改变执行结果(这也是as-if-serial语义容许对存在控制依赖的操做进行重排序的缘由);但在多线程程序中,对存在控制依赖的操做重排序,可能会改变程序的执行结果。

19.数据竞争的定义:在一个线程中写一个变量,在另外一个线程中读同一个变量,并且写和读没有经过同步来排序。

20.若是程序是正确同步的,程序的执行将具备顺序一致性--即该程序的执行结果与程序在顺序一致性模型中的执行结果相同。

21.顺序一致性内存模型的两大特性:

一个线程中的全部操做必须按照程序的顺序来执行; (无论程序是否同步)全部线程都只能看到一个单一的操做执行顺序。在顺序一致性内存模型中,每一个操做都必须原子执行且马上对全部线程可见。 22.在Java内存模型中,临界区内的代码能够重排序(但Java内存模型不容许临界区内的代码逸出到临界区以外,那样会破坏监视器的语义)。Java内存模型会在退出临界区和进入临界区这两个关键时间点作一些特殊处理,使得线程在这两个时间点具备与顺序一致性模型相同的内存视图。

23.Java内存模型与顺序一致性模型的差异:

顺序一致性模型保证单线程内的操做会按程序的顺序执行,而Java内存模型不保证单线程内的操做按照程序顺序执行; 顺序一致性模型保证全部线程只能看到一致的操做执行顺序,而Java内存模型不保证全部线程能看到一致的操做执行顺序; Java内存模型不保证对64位的long和double类型变量写操做具备原子性,而顺序一致性模型保证对全部的内存读写操做都具备原子性。 24.64位long和double类型变量的写操做不具备原子性的缘由是:在一些32位的机器上,对于内存的写入操做,每次只能是原子的执行一段32位内存的写入,对于64位的变量,须要分两次原子的写入操做进行,于是总体来讲是不具有原子性的。

25.在jdk1.5版本之前,一个64位long/double类型变量的读/写操做能够拆分为两个32位的读/写操做来进行;在jdk1.5版本(含)以后,64位的long/double类型变量的写入操做可以拆分为两个32位的写操做来进行,而读操做则经过jvm保证其具备原子性。

26.volatile变量的特性:

可见性。一个volatile变量的读,老是可以看到最后一个线程对volatile变量写入的值; 原子性。对任意单个volatile变量的读/写具备原子性,可是相似于volatile++这种复合操做不具有原子性。 27.当读一个volatile变量时,Java内存模型会把该线程对应的本地内存置为无效,而从主内存中读取共享变量。

28.volatile写和volatile读的内存语义:

线程A写一个volatile变量,实质上是线程A向接下来所要读这个volatile变量的线程发出了(其对共享变量所做修改的)信息; 线程B读一个volatile变量,实质上是线程B接收了以前某个线程发出的(在写这个volatile变量以前对共享变量所作修改的)信息; 线程A写一个volatile变量,随后线程B读一个volatile变量,这个过程实质上是线程A经过主内存向线程B发送消息。 29.volatile变量读写操做重排序的规则:

当第二个操做是volatile写时,无论第一个操做是什么,都不能重排序。这个规则确保volatile写以前的操做不会被重排序到volatile写以后。 当第一个操做是volatile读时,无论第二个操做是什么,都不能重排序。这个规则确保volatile读以后的操做不会被重排序到volatile读以前。 当第一个操做是volatile写,第二个操做是volatile读时,不能重排序。 30.Java内存模型对于volatile变量插入内存屏障的策略:

在每一个volatile写操做以前插入一个StoreStore屏障; 在每一个volatile写操做后面插入一个StoreLoad屏障; 在每一个volatile读操做后面插入一个LoadLoad屏障; 在每一个volatile读操做后面插入一个LoadStore屏障; 31.final域的重排序规则:

在构造函数中对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序; 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操做之间不能重排序。 32.经过为final域增长写和读重排序规则,能够为Java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在对象构造过程当中没有逸出),那么不须要使用同步(指lock和volatile的使用)就能够保证任意线程均可以看到这个final域在构造函数中被初始化以后的值。

33.对于会改变程序执行结果的重排序,Java内存模型要求编译器和处理器必须禁止这种重排序;对于不会改变程序执行结果的重排序,Java内存模型对编译器和处理器不作要求(Java内存模型容许这种重排序)。

34.Java内存模型对编译器和处理器已经尽量少。从上面的分析能够看出,Java内存模型实际上是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。

35.若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前;两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。若是重排序以后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说Java内存模型容许这种重排序)。

36.jsr-133中定义的happens-before规则:

程序顺序规则:一个线程中的每一个操做,happens-before于该线程中的任意后续操做; 监视器锁规则:对一个锁的解锁,happens-before于后续对该锁的加锁; volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读; 传递性:若是A happens-before B,且B happens-before C,那么A happens-before C; start规则:若是线程A执行操做ThreadB.start()(启动线程B),那么A线程中的ThreadB.start()操做happens-before于线程B中的任意操做; join规则:若是线程A执行ThreadB.join()并成功返回,那么线程B中的任意操做happens-before于线程A从ThreadB.join()操做成功返回。 37.当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。

38.Daemon属性须要在启动线程以前设置,不能再启动线程以后设置。

39.Daemon线程被用做完成支持性工做,可是在Java虚拟机退出时Daemon线程中的finally块并不必定会执行,于是在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

40.线程start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应当即启动调用start()方法的线程。

41.在启动一个线程时,最好为这个线程设置一个名称,这样在进行jstack分析时可方便问题排查。

42.调用wait()、notify()和notifyAll()须要注意的细节:

使用wait()、notify()和notifyAll()时须要对调用对象加锁; 调用wait()方法后,线程状态由RUNNING变成WAITING,并将当前线程放置到对象的等待队列; notify()和notifyAll()调用以后,线程依旧不会从wait()返回,须要调用notify()或notifyAll()的线程释放锁以后,等待线程才有机会从wait()返回; notify()将等待队列中的一个线程从等待队列移到同步队列中,而notifyAll()则是将等待队列中全部线程从等待队列移到同步队列中,被移动的线程状态由WAITING变为BLOCKED; 从wait()方法返回的前提是得到了调用对象的锁。 43.Wait Notify的经典范式: ①等待方遵循以下规则:

获取对象的锁; 若是条件不知足,那么调用对象的wait()方法,被通知后仍要检查条件; 条件知足则执行对应的逻辑; 对应伪代码以下: synchronized (对象) { while (条件不知足) { 对象.wait();
} 对象的处理逻辑 } ②通知方遵循以下原则:

得到对象的锁; 改变条件; 通知全部等待在对象上的线程; 对应伪代码以下: synchronized (对象) { 改变条件; 对象.notifyAll(); } 44.等待超时模式的伪代码:

public synchronized Object get(long mills) throws InterruptedException { long future = System.currentTimeMillis() + mills; long remaining = mills;

// 当超时大于0而且result返回值不知足要求
while ((result == null) && remaining > 0) {
    wait(remaining);
    remaining = future - System.currentTimeMillis();
}

return result;
复制代码

} 45.若是在绝对时间上,先对锁进行获取的请求必定先被知足,那么这个锁是公平的,反之,是不公平的。事实上,公平锁的效率没有非公平锁的效率高,可是公平锁可以减小“饥饿”发生的几率。

46.实现锁的重进入须要解决的问题:

线程再次获取锁。锁须要去识别获取锁的线程是否为当前占据锁的线程,若是是,则再次成功获取; 锁的最终释放。线程重复n此获取了锁,随后在第n此释放该锁后,其余线程可以获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时,表示锁已经成功释放。 47.读写锁在同一时刻能够容许多个线程访问,可是在写线程访问时,全部的读线程和其余写线程均被阻塞。读写锁维护了一对锁:一个读锁和一个写锁,经过分离读锁和写锁,使得并发性相比通常的排他锁有了很大提高。

48.读写锁将变量切分红了两个部分,高16位表示读,低16位表示写。

49.ReentrantReadWriteLock使用位来表征读写锁状态的方式:假设当前同步状态值为S,写状态等于S&0x0000FFFF(将高16位所有抹去),读状态等于S>>>16(无符号补0右移16位)。当写状态增长1时,等于S+1,当读状态增长1时,等于S+(1<<16),也就是S+0x00010000

50.根据上述状态划分得出一个结论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。

51.对于ReentrantReadWriteLock,只有等待其余读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其余读写线程的后续访问均被阻塞。

52.锁降级指的是把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

53.LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。

54.通常都会将Condition对象做为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其余线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并在返回前获取到了锁。

55.在Condition的等待队列中并无使用CAS算法来保证新加入的节点是加入到节点尾部,由于调用Condition.await()方法的线程一定是获取了锁的线程,其是经过锁来保证线程安全的。

56.多线程会致使HashMap的Entry链表造成环形数据结构,一旦造成环形数据结构,Entry的next节点将永不为空,就会产生死循环获取Entry。

57.HashTable容器使用synchronized来保证线程安全,当一个线程访问HashTable的同步方法时,其余线程访问HashTable的任何方法都将进入阻塞或轮询状态。

58.ConcurrentHashMap容器中有多把锁,每一把锁只锁定一部分数据,多线程访问时,对于不一样段数据的访问不须要竞争同一把锁,从而能够有效的提升并发访问效率,这就是ConcurrentHashMap的锁分段技术。

59.ConcurrentHashMap在为Segament扩容的时候,首先会建立一个长度是原来两倍的数组,而后经过再散列将原数组中的数据散列到新的数组中。为了高效,ConcurrentHashMap不会对Segament进行扩容。

60.ConcurrentHashMap计算size的方式以下:在Segament对象中有一个字段count,该字段记录了当前Segament中全部的键值对的数目,在调用size()方法时经过累加每一个Segament中的count,从而获得总的count,可是因为在累加的过程当中可能以前累加的count发生了变化,于是ConcurrentHashMap经过两次累加每一个Segament中的count,并比较两次获得的数值是否相同,若是相同则返回,不然将全部对表进行结构性修改的操做都锁住,而后进行一次累加获得结果。

61.阻塞队列中方法说明: 阻塞队列方法说明

62.各个阻塞队列说明: ①ArrayBlockingQueue:其是用一个数组实现的有界阻塞队列,按照先进先出的方式排序; ②LinkedBlockingQueue:其是一个用链表实现的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE; ③PriorityBlockingQueue:其是一个支持优先级的无界阻塞队列,默认状况下按照天然顺序升序排序,若是指定了Comparator则按照指定的排序方式排序,须要注意的是其不能保证同优先级元素的顺序; ④DelayedQueue:其是一个支持延迟获取元素的无界阻塞队列,队列使用PriorityQueue实现,队列中的元素必须实现Delayed接口,在建立元素时能够指定多久才能从队列中获取到元素,只有延迟期满时才能从队列中提取元素; ⑤SynchronousQueue:其是一个不存储元素的阻塞队列,每一个put操做必须等待一个take操做,不然不能添加元素,其也支持公平和非公平策略访问队列(该队列的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue); ⑥LinkedTransferQueue:其是一个有链表结构组成的无界阻塞TransferQueue队列,相比于其余阻塞队列,其多了tryTransfer和transfer方法,对于transfer方法,若是当前有正在等待的消费者,transfer方法会将生产者传入的元素当即transfer到消费者,若是没有等待的消费者,其会将元素放入到队列的tail节点,对于tryTransfer方法,其是用来试探生产者传入的元素可否直接传给消费者,若是有则返回true,不然返回false,对于有时间限的tryTransfer方法,若是有正在等待的消费者,该方法直接返回true,若是没有,则等待设定的时间,仍是没有则返回false; ⑦LinkedBlockingDeque:其是一个由链表结构组成的双向阻塞队列,便可以从队列头和队列尾两端插入和移除元素,双向队列多了一个队列入队和出队的口,于是在多线程入队和出队时也就减小了一半的竞争。

63.DelayedQueue的两个应用场景: ①缓存系统的设计:能够用DelayedQueue设计缓存系统,将元素放置到DelayedQueue中后,若是在指定时间以后能从DelayedQueue中获取到元素,那么说明元素过时了; ②定时任务调度:能够将一些定时任务放到DelayedQueue中,经过一个线程轮询队列,当从队列中取到元素以后则执行该任务。

64.实现Delayed接口的方式(分三步): ①在建立对象时,初始化基本数据,使用time记录当前对象延迟到何时使用,使用sequenceNumber来标识元素在队列中的前后顺序; ②实现getDelay方法,该方法返回当前元素还须要延时多长时间,单位是纳秒; ③实现compareTo方法来指定元素的顺序,让延时时间最长的放在队列的末尾。

65.park方法会阻塞当前线程,其在如下四种状况下会返回:

与park对应的unpark方法执行或已经执行,“已经执行”是指unpark先执行,而后执行park方法; 线程被中断; 等待完time参数指定的毫秒数; 异常现象发生时,这个异常现象没有任何缘由。 66.Fork/Join框架是一个把大任务分隔成若干个小任务,最终汇总每一个小任务结果后获得大任务结果的框架。

67.工做窃取算法:工做窃取算法是指某个线程从其余队列里窃取任务来执行,为了减小与正常执行线程之间的竞争,其通常是使用双端队列来完成。在Fork/Join框架中,通常fork出来的子任务是放在不一样的队列中的,而且每一个队列有一个线程执行其中的任务,若是某个线程执行得较快,任务已作完,其不能一直等待着其余的队列线程执行完任务以后才合并结果,于是须要使用工做窃取算法使其同步执行其余队列的任务。

68.工做窃取算法的优势:充分利用线程进行并行计算,减小线程间的竞争;缺点:在某些状况下仍是存在竞争,好比双端队列只有一个任务时。而且该算法消耗了更多的系统资源,好比建立了多个线程和多个双端队列。

69.Fork/Join框架的设计:

步骤一:分割任务。首先须要一个fork类来吧大任务分割成子任务,有可能子任务仍是很大,因此还须要不停的分割,直到分割出的子任务足够小; 步骤二:执行任务并合并结果:分割的子任务分别放在双端队列里,而后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果统一放在一个队列里,启动一个线程从队列里拿数据,而后合并这些数据。 70.Fork/Join框架的主要类:

ForkJoinTask:该类表示要执行的任务,使用时咱们主要继承其两个子类:RecursiveAction和RecursiveTask。RecursiveAction表示没有返回值的任务,RecursiveTask表示有返回值的任务; ForkJoinPool:执行ForkJoinTask的类。 71.ForkJoinTask在执行时,若是抛出异常,那么主线程是没法捕获到该异常的,于是该类提供了一个isCompletedAbnormally方法,用于检测是否正常完成,而且提供了一个getException方法,若是当前任务被取消了,那么该方法将返回CancellationException,若是任务没有完成或抛出异常,则返回null。

72.AtomicIntegerArray的构造函数能够传入一个整形数组,也能够传入一个length,其会在内部将传入的数组复制一份或者新建一个length长度的数组,于是对AtomicIntegerArray的操做不会影响原数组的值。

73.原子更新字段类AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference使用时须要注意两点:

由于原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()建立一个更新器,而且须要设置想要更新的类和属性; 更新类的字段(属性)必须使用public volatile修饰符。 74.AtomicStampedReference是原子更新带有版本号的引用类型,该类将整数值与引用关联起来,更新引用的时候也会原子的更新版本号,从而解决了使用CAS进行原子更新时可能出现的ABA问题。

75.CountDownLatch容许一个或多个线程等待其余线程完成任务以后才会继续往下执行,其内部维护了一个整数变量,每次调用countDown()方法,该整数变量都会减一,而另外的调用await()方法的线程将会被阻塞,直到屡次调用countDown()方法后内部维护的整数变量值减为0了。

76.CountDownLatch在构造时须要传入一个整数,每次调用该类对象的countDown()方法时内部维护的该整数就会减一,而CountDownLatch::await()方法会阻塞当前线程,直到其内部维护的整数值降为0。这里countDown()方法的调用能够是一个线程调用屡次,也能够是多个线程每一个调用一次,只要调用该整数值次便可。

77.CountDownLatch构造函数传入的整数值必须大于等于0.

78.CyclicBarrier的做用是让一组线程到达一个屏障(也能够说是一个同步点)时阻塞,直到全部的线程都到达了该屏障,而后才让全部线程继续往下执行。

79.CyclicBarrier另外提供了一个以下构造函数CyclicBarrier(int parties, Runnable barrier-action),第一个参数仍是指将要阻塞的线程数量,而第二个参数指定了一个任务,该任务会由第一个到达屏障的线程执行,可是其是在全部阻塞的线程执行当前任务到达屏障以后准备继续执行后续任务时才执行,也即全部阻塞的线程从await()方法中返回的时候。

80.Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它经过协调各个线程,以保证合理的使用公共资源。

81.线程池的优势:

下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗; 提升响应速度。当任务到达时,任务能够部须要等到线程建立就能当即执行; 提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一分配、调优和监控。 82.提交任务时线程池的处理流程以下: ①线程池判断核心线程池里的线程是否都在执行任务。若是不是,则建立一个新的线程来执行任务,若是核心线程池里的线程都在执行任务,则进入下一个流程; ②线程池判断工做队列是否已满。若是工做队列没有满,则将新提交的任务存储在这个工做队列里,若是工做队列满了,则进入下一个流程; ③线程池判断线程池的线程是否都处于工做状态,若是没有,则建立一个新的线程来执行任务。若是已经满了,则交给饱和策略来处理这个任务。

83.CachedThreadPool是大小无界的线程池,适用于执行不少的短时间异步任务的小程序,或者是负载较轻的服务器。

84.FixedThreadPool适用于为了知足资源管理的需求,而须要限制当前线程数量的应用场景,它适用于负载比较重的服务器。

85.SingleThreadExecutor适用于须要保证顺序执行各个任务,而且在任意时间点,不会有多个线程是活动的应用场景。

86.使用无界队列对FixedThreadPool带来了以下影响:

当线程池中的线程达到corePoolSize以后,新任务将在无界队列中等待,所以线程池中的线程数不会超过corePoolSize,于是设置的maximumPoolSize将是一个无效参数; 因为任务会一直添加到无界队列中,而且除了corePoolSize个线程以后不会建立新的线程,于是设置的keepAliveTime和RejectedExecutionHandler将不会产生影响。 87.CachedThreadPool底层是建立了一个ThreadPoolExecutor,其corePoolSize传入的是0,maxPoolSize是Integer.MAX_VALUE,空闲等待时间是60s,而且其使用的SynchronousQueue来处理任务。这里SynchronousQueue内部是没有容量保存任何任务的,每一个线程提交了一个任务以后必须有一个线程取出任务。从这些参数能够看出,初始状态下CachedThreadPool中是没有任何线程的,当提交一个新的任务以后若是当前有空闲的线程,则该线程取出任务执行,若是没有线程空闲,那么就会建立一个新的线程,也就是说若是须要执行的任务很是多,那么建立的线程数将会急剧上升。于是CachedThreadPool适用于多任务数,而且执行时间较短的场景。

88.FutureTask表示一个将要执行的任务,其不只实现了Future接口,还实现了Runnable接口,于是能够将其当作一个任务提交给Executor执行。FutureTask的任务有三种状态:未启动、已启动和已完成。对于已完成状态,其可能有三种状态:FutureTask::run()方法正常结束,run()方法被取消而结束,run()方法抛出异常结束。

89.当FutureTask处于未启动或已启动状态时,FutureTask::get()方法将致使线程阻塞;当FutureTask处于已完成状态时,FutureTask::get()方法将致使线程当即返回或抛出异常。

90.当FutureTask处于未启动状态时,FutureTask.cancel()方法将直接取消该任务的执行;当其处于已启动状态时,若是执行FutureTask::cancel(true),那么将以中断此任务执行线程的方式中止任务执行,若是执行FutureTask::cancel(false),那么其不会对正在执行此任务的线程产生影响;当FutureTask处于已完成状态时,执行FutureTask::cancel()方法将返回false。

91.在实际应用中,若是须要继承某个已有的类或抽象类,而基于“组合优先于继承”的原则,咱们能够在要建立的类中声明一个内部类来继承须要继承的类,而后将当前类与内部类使用组合来解耦。

92.生产者和消费者是经过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是经过阻塞队列来进行通讯,因此生产者生产完数据以后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就至关于一个缓冲区,平衡了生产者和消费者的处理能力。

相关文章
相关标签/搜索