JAVA 并发编程原理与实战
1. volatile
在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能马上读到这个修改的值。
1.1 应用场景
1.1.1 状态标志
1.1.2 双重检查锁定与延迟初始化
1.2 实现原理
基于CPU的MESI(Modified, Exclusive, Shared, Invalid)缓存一致性。
1) cpu0开始读取变量x;cpu0将变量x从内存读取到自己的缓存行,此时cpu0缓存行的状态为Exclusive。
2) cpu1与cpu2也开始读取变量x。cpu1与cpu2分别从内存读取变量x到自己的缓存行,此时cpu1和cpu2的缓存行状态为Shared;cpu0监听到cpu1、cpu2读取了变量x,遂将缓存行状态改为Shared。
3) cpu0开始修改变量x。cpu0将自己缓存行的变量x改为5,此时cpu0缓存行的状态变为Modified;cpu1和cpu2监听到cpu0的缓存行状态变为Modified,遂将自己的缓存行状态改为Invalid。
4) cpu1开始读取变量x。cpu1发现自己的缓存行状态为Invalid,准备重新将变量x从内存读取到缓存行;此时cpu0监听到cpu1准备读取变量x,遂将缓存行中的变量x值回写到内存中,并通知cpu1内存上的变量x已更新;cpu1收到通知,将变量x从内存读取到自己的缓存行;此时cpu0和cpu1的缓存行状态均变为Shared
2. Semaphore
用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源。
2.1 应用场景
2.1.1 并发数控制
2.2 实现原理
基于AbstractQueueSynchronizer队列型同步器(简称AQS),采用的是AQS的共享式获取锁方式(同一时刻可以有多个线程同时获得锁)。
2.2.1 AQS队列型同步器
AQS是用来构建锁或其他同步组件的基础框架,它使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
AbstractQueueSynchronizer数据结构图
2.2.2 AQS独占式获取锁
独占式获取锁,既同一时刻只能有一个线程成功获取同步状态。
AQS独占式获取锁流程图
2.2.3 AQS共享式获取锁
共享式获取锁,既同一时刻可以有多个线程同时获取同步状态。
AQS共享式获取锁流程图
3. CountDownLatch
允许一个线程或多个线程等待其他N个线程全部执行完成。
3.1 应用场景
3.1.1 等待多个异步任务完成
3.2 实现原理
基于AbstractQueueSynchronizer队列型同步器(简称AQS),采用的是AQS的共享式获取锁方式(同一时刻可以有多个线程同时获得锁)。
4. Atomic*
保证多线程操作的原子性,包括AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray、AtomicReference、AtomicReferenceFieldUpdater、AtomicMarkableReference。
4.1 应用场景
4.1.1 生成序列号
4.1.2 计数器
4.2 实现原理
调用JVM的Unsafe中的CAS(compareAndSwap)方法,先取得现有的值,检查现有的值是否被其他线程修改,若未被修改,则将值更新为新的值;若值已被修改,则进入自旋,直到值更新成功。
5. ReentrantLock
可重入锁,表示该锁能够支持一个线程对资源的重复加锁,任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞。
5.1 应用场景
5.1.1 有超时的获取锁
5.1.2 与Condition配合实现线程的等待通知
5.2 实现原理
基于AbstractQueueSynchronizer队列型同步器。当一个线程尝试去获取锁时,发现锁已被获取,则判断获取锁的线程是否为当前线程,若是,则再次成功获取到锁。
6. BlockingQueue
阻塞队列是一个支持阻塞插入、阻塞移除操作的队列:
阻塞插入(put):当队列满时,执行插入操作的线程会阻塞住,直到队列不满(有元素从队列移除)
阻塞移除(take):当队列为空时,抚摩行移除操作的线程会阻塞住,直到队列不为空(有元素插入到队列)
非阻塞插入(offer):当队列满时,直接返回false,表示插入失败;否则返回true,表示插入成功
非阻塞移除(poll):当队列为空时,直接返回null;否则返回一个元素
常用的阻塞队列有以下几种:
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue:一个由链接由链表结构组成的有界阻塞队列
DelayQueue:一个支持按时间排序的无界阻塞队列
SynchronousQueue:一个不存储元素的阻塞队列
6.1 应用场景
6.1.1 使用LinkedBlockingQueue实现FixedThreadPool
FixedThreadPool运示意图
6.1.2 使用DelayQueue实现ScheduledThreadPool
ScheduledThreadPool运行示意图
6.2 实现原理
6.2.1 LinkedBlockingQueue
5) 插入、移除使用不同的锁,采用等待通知模式,通过Condition通知阻塞中的线程。
6) 阻塞插入时,若队列已满,则阻塞当前线程,等待队列非满通知(有元素被移出队列);如果当前插入的为第一个元素,则发出队列非空通知,唤醒阻塞中的执行移除操作的线程。
7) 阻塞移除时,若队列为空,则阻塞当前线程,等待队列非空通知(有元素插入队列);如果当前移除的为最后一个元素,则发出队列非满通知,唤醒阻塞中的执行插入操作的线程。
6.2.2 DelayQueue
1) 插入、移除使用同一把锁,使用PriorityQueue来保存队列元素。因为PriorityQueue非线程安全,所以需要使用锁来保证多线程对其操作的安全性。PriorityQueue内部采用小顶堆结构来保存元素,剩余时间最小的元素在最顶端,每次插入、移除元素均会重新调整小顶堆。
2) 由于该队列为无界队列,插入时不阻塞,直接调用PriorityQueue的offer方法插入元素;如果插入的元素为队列的第一个元素,则发出队列非空通知,唤醒阻塞中的执行移除操作的线程。
3) 阻塞移除时,如果队列为空,则阻塞当前线程,等待队列非空通知(有元素插入队列);如果队列的第一个元素还未到执行时间,则阻塞当前线程直到执行时间到;
版权声明: 本文为 InfoQ 作者【Geek_53983e】的原创文章。
原文链接:【http://xie.infoq.cn/article/bbd7f4dd78442f578308a7551】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论