并发容器
-
BlockingQueue
1.1 ArrayBlockingQueue
数字组实现的环形队列,核⼼为1个ReentrantLock锁外加两个条件Condition notEmpty和Condition notFull
1.2 LinkedBlockingQueue
是⼀种基于单向链表的阻塞队列,因为队头和队尾是2个指针分开操作的,所以⽤了2把锁+2个条件,同时有1个AtomicInteger的原⼦变量记录count数。
1.3 PriorityBlockingQueue
按照元素的优先级从⼩到⼤出队列的,实现Comparable接⼝,核心是堆排序
1.4 DelayQueue
按延迟时间从⼩到⼤出队的PriorityQueue,实现Delayed接⼝,Delayed接口继承了 Comparable 接⼝
1.5 SynchronousQueue
先调put(…),线程会阻塞;直到另外⼀个线程调⽤了take(),两个线程才同时解锁,反之亦然。两种实现:TransferQueue(遵循先到先配对的原则),TransferStack(遵循后到先配对的原则) -
BlockingDeque
⼀个阻塞的双端队列接⼝,该接⼝只有⼀个实现,就是
LinkedBlockingDeque,核⼼是⼀个双向链表 -
CopyOnWrite
CopyOnWrite指在“写”的时候,不是直接“写”源数据,⽽是把数据拷⻉⼀份进⾏修改,再通过悲观锁或者乐观锁的
⽅式写回。为了在“读”的时候不加锁。
3.1 CopyOnWriteArrayList
核⼼数据结构是⼀个数组
3.2 CopyOnWriteArraySet
内部是封装的⼀个 CopyOnWriteArrayList,并保证所有元素都不重复 -
ConcurrentLinkedQueue/Deque
AQS内部的阻塞队列实现原理:基于双向链表,通过对head/tail进⾏CAS操作,实现⼊队和出队。 -
ConcurrentHashMap
HashMap通常的实现⽅式是“数组+链表”,这种⽅式被称为“拉链法”。ConcurrentHashMap在这个基本原理之上进⾏了各种优化。引⼊了红⿊树。
如果头节点是Node类型,则尾随它的就是⼀个普通的链表;如果头节点是TreeNode类型,它的后⾯就是⼀颗红⿊树,TreeNode是Node的⼦类。
链表和红⿊树之间可以相互转换:初始的时候是链表,当链表中的元素超过某个阈值时,把链表转换成红⿊树;反之,当红⿊树中的元素个数⼩于某个阈值时,再转换为链表。
为什么要做这种设计?
5.1. 使⽤红⿊树,当⼀个槽⾥有很多元素时,其查询和更新速度会⽐链表快很多,Hash冲突的问题由此得到较好 的解决。
5.2. 加锁的粒度,并⾮整个ConcurrentHashMap,⽽是对每个头节点分别加锁,即并发度,就是Node数组的⻓ 度,初始⻓度为16。
5.3. 并发扩容,这是难度最⼤的。当⼀个线程要扩容Node数组的时候,其他线程还要读写
- ConcurrentSkipListMap/Set
6.1 ConcurrentSkipListMap
是 key 有序的,实现了NavigableMap接⼝,此接⼝⼜继承了SortedMap接⼝。核心数据结构是跳查表。
6.2 ConcurrentSkipListSet
ConcurrentSkipListSet只是对ConcurrentSkipListMap的简单封装
同步⼯具类
- Semaphore
Semaphore也就是信号量,提供了资源数量的并发访问控制。类似于⼤学⽣到⾃习室抢座,资源有限,先到先得,后到排队。
new Semaphore(X); 初始化资源
semaphore.acquire(); 占领资源
semaphore.release(); 释放资源
- CountDownLatch
假设⼀个主线程要等待5个 Worker 线程执⾏完才能退出,可以使⽤CountDownLatch来实现
new CountDownLatch(X); 初始化
latch.await();当前程序等待
latch.countDown(); 每个Worker线程执行完打报告
await()调⽤的是AQS 的模板⽅法,只要state != 0,调⽤await()⽅法的线程便会被放⼊AQS的阻塞队列,进⼊阻塞状态。
总结:由于是基于AQS阻塞队列来实现的,所以可以让多个线程都阻塞在state=0条件上,通过countDown()⼀直减 state,减到0后⼀次性唤醒所有线程。如下图所示,假设初始总数为M,N个线程await(),M个线程countDown(),减到0之后,N个线程被唤醒。
- CyclicBarrier
Barrier——栅栏。类似于开会,等人到齐了才能开始。
new CyclicBarrier(X); 初始化
barrier.await(); 等待(人到齐自动唤醒)
CyclicBarrier基于ReentrantLock+Condition实现
- CyclicBarrier是可以被重⽤的。一个线程可以多次调用await(),等第一批到齐,完成后,又进入下一轮次的等待。每⼀轮被称为⼀个Generation,就是⼀次同步点。
- CyclicBarrier 会响应中断。线程没有到⻬,如果有线程收到了中断信号,所有阻塞的线程也会被唤醒(breakBarrier()⽅法)。
- Exchanger
Exchanger⽤于线程之间交换数据
new Exchanger<>(); 初始化
exchanger.exchange(X); 线程A调用change方法,获得Y
exchanger.exchange(Y); 线程B调用change方法,获得X
核⼼机制和Lock⼀样,也是CAS+park/unpark。
- Phaser
功能⽐CyclicBarrier和CountDownLatch更加强⼤
⽤Phaser替代CountDownLatch
new Phaser(X); 初始化
phaser.awaitAdvance(0); 主线程等待
phaser.arrive(); 每个子线程完成打报告
⽤Phaser替代CyclicBarrier
new Phaser(X); 初始化
phaser.arriveAndAwaitAdvance(); 到达同步点,等待其他线程
Phaser新特性
特性1:动态调整线程个数
CyclicBarrier 所要同步的线程个数是在构造⽅法中指定的,之后不能更改,⽽ Phaser 可以在运⾏期间动态地调
整要同步的线程个数。Phaser 提供了下⾯这些⽅法来增加、减少所要同步的线程个数。
register() // 注册⼀个
bulkRegister(int parties) // 注册多个
arriveAndDeregister() // 解除注册
特性2:层次Phaser
多个Phaser可以组成树状结构,可以通过在构造⽅法中传⼊⽗Phaser来实现。
作用场景:类似于任务分解,一个大任务分解成多个小任务,等每个小任务完成了才能执行大任务
原理:CAS操作和Treiber Stack(不再是AQS了)
原子操作类(Atomic类)
- AtomicInteger和AtomicLong
核心:使用的Unsafe类的方法,该方法基于CAS实现 - AtomicBoolean和AtomicReference
核心:AtomicBoolean调用的AtomicInteger类的方法,传入参数变成0/1,
AtomicReference和AtomicInteger类似,只是泛型从基础类型包装类变成了Object类。 - AtomicStampedReference和AtomicMarkableReference
使用场景:解决AtomicReference所解决不了的ABA问题
核心:
AtomicMarkableReference内部加了个boolean的标识,通过标识辨别值是否有被更改(一定程度解决ABA问题)
AtomicStampedReference内部加了个int版本号,通过版本号辨别值是否有被更改(完全解决ABA问题) - AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和
AtomicReferenceFieldUpdater
为什么需要AtomicXXXFieldUpdater?
如果⼀个类是⾃⼰编写的,则可以在编写的时候把成员变量定义为Atomic类型。但如果是⼀个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原⼦操作,就需要AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。
案例:
AtomicIntegerFieldUpdater<A> ageField = AtomicIntegerFieldUpdater.newUpdater(A.class, "age");
A a = new A();
a.setAge(0);
int nextAge = ageField.getAndIncrement(a);
System.out.println(nextAge); //1
限制条件:类成员变量不能是私有的、必须是volatile修饰的
解决办法:使用代理类
- AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray
Concurrent包提供了AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray三个数组元素的原⼦操作。注意,这⾥并不是说对整个数组的操作是原⼦的,⽽是针对数组中⼀个元素的原⼦操作⽽⾔。
核心:CAS⽅法直接调⽤VarHandle中native的getAndAdd⽅法 - Striped64与LongAdder
从JDK 8开始,针对Long型的原⼦操作,Java⼜提供了LongAdder、LongAccumulator;针对Double类型,Java提供了DoubleAdder、DoubleAccumulator。他们都继承了Striped64。
核心:AtomicLong内部是⼀个volatile long型变量,这个变量被拆分成了多份(Cell),由多个线程对这个变量进⾏CAS操作。写操作使用负载均衡策略,一个线程只修改其中某一份;读操作只需将全部的值相加即可。其中为了解决“伪共享问题”,每个Cell使用了@jdk.internal.vm.annotation.Contended修饰来达到“缓存填充”的目的,通俗说来就是让每个Cell在CPU缓存里都是单独的一行。 - LongAccumulator
LongAccumulator的原理和LongAdder类似,只是功能更强⼤,可以⾃⼰定义⼀个⼆元操作符,并且可
以传⼊⼀个初始值。 - DoubleAdder与DoubleAccumulator
DoubleAdder 其实也是⽤ long 型实现的,因为没有 double 类型的 CAS ⽅法。