jdk的JUC包(java.util.concurrent)提供大量Java并发工具提供使用,基本由Doug Lea编写,很多地方值得学习和借鉴,是进阶升级必经之路
本文从JUC包中常用的对象锁、并发工具的使用和功能特性入手,带着问题,由浅到深,一步步剖析并发底层AQS抽象类具体实现
名词解释
1 AQS
AQS是一个抽象类,类全路径java.util.concurrent.locks.AbstractQueuedSynchronizer,抽象队列同步器,是基于模板模式开发的并发工具抽象类,有如下并发类基于AQS实现:
2 CAS
CAS是Conmpare And Swap(比较和交换)的缩写,是一个原子操作指令
CAS机制当中使用了3个基本操作数:内存地址addr,预期旧的值oldVal,要修改的新值newVal 更新一个变量的时候,只有当变量的预期值oldVal和内存地址addr当中的实际值相同时,才会将内存地址addr对应的值修改为newVal
基于乐观锁的思路,通过CAS在不断尝试和比较,可以对变量值线程安全地更新
3 线程中断
线程中断是一种线程协作机制,用于协作其他线程中断任务的执行
当线程处于阻塞等待状态,例如调用了wait()、join()、sleep()方法之后,调用线程的interrupt()方法之后,线程会马上退出阻塞并收到InterruptedException;
当线程处于运行状态,调用线程的interrupt()方法之后,线程并不会马上中断执行,需要在线程的具体任务执行逻辑中通过调用isInterrupted() 方法检测线程中断标志位,然后主动响应中断,通常是抛出InterruptedException
对象锁特性
下面先介绍对象锁、并发工具有哪些基本特性,后面再逐步展开这些特性如何实现
1 显式获取
以ReentrantLock锁为例,主要支持以下4种方式显式获取锁
-
(1) 阻塞等待获取
ReentrantLock lock = new ReentrantLock();
// 一直阻塞等待,直到获取成功
lock.lock();
-
(2) 无阻塞尝试获取
ReentrantLock lock = new ReentrantLock();
// 尝试获取锁,如果锁已被其他线程占用,则不阻塞等待直接返回false
// 返回true - 锁是空闲的且被本线程获取,或者已经被本线程持有
// 返回false - 获取锁失败
boolean isGetLock = lock.tryLock();
-
(3) 指定时间内阻塞等待获取
ReentrantLock lock = new ReentrantLock();
try {
// 尝试在指定时间内获取锁
// 返回true - 锁是空闲的且被本线程获取,或者已经被本线程持有
// 返回false - 指定时间内未获取到锁
lock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// 内部调用isInterrupted() 方法检测线程中断标志位,主动响应中断
e.printStackTrace();
}
-
(4) 响应中断获取
ReentrantLock lock = new ReentrantLock();
try {
// 响应中断获取锁
// 如果调用线程的thread.interrupt()方法设置线程中断,线程退出阻塞等待并抛出中断异常
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
2 显式释放
ReentrantLock lock = new ReentrantLock();
lock.lock();
// ... 各种业务操作
// 显式释放锁
lock.unlock();
3 可重入
已经获取到锁的线程,再次请求该锁可以直接获得
4 可共享
指同一个资源允许多个线程共享,例如读写锁的读锁允许多个线程共享,共享锁可以让多个线程并发安全地访问数据,提高程序执效率
5 公平、非公平
公平锁:多个线程采用先到先得的公平方式竞争锁。每次加锁前都会检查等待队列里面有没有线程排队,没有才会尝试获取锁。非公平锁:当一个线程采用非公平的方式获取锁时,该线程会首先去尝试获取锁而不是等待。如果没有获取成功,才会进入等待队列
因为非公平锁方式可以使后来的线程有一定几率直接获取锁,减少了线程挂起等待的几率,性能优于公平锁
AQS实现原理
1 基本概念
(1) Condition接口
类似Object的wait()、wait(long timeout)、notify()以及notifyAll()的方法结合synchronized内置锁可以实现可以实现等待/通知模式,实现Lock接口的ReentrantLock、ReentrantReadWriteLock等对象锁也有类似功能:
Condition接口定义了await()、awaitNanos(long)、signal()、signalAll()等方法,配合对象锁实例实现等待/通知功能,原理是基于AQS内部类ConditionObject实现Condition接口,线程await后阻塞并进入CLH队列(下面提到),等待其他线程调用signal方法后被唤醒
(2) CLH队列
CLH队列,CLH是算法提出者Craig, Landin, Hagersten的名字简称
AQS内部维护着一个双向FIFO的CLH队列,AQS依赖它来管理等待中的线程,如果线程获取同步竞争资源失败时,会将线程阻塞,并加入到CLH同步队列;当竞争资源空闲时,基于CLH队列阻塞线程并分配资源
CLH的head节点保存当前占用资源的线程,或者是没有线程信息,其他节点保存排队线程信息
CLH<