之前写了篇文章介绍了synchronized的原理《Java synchronized 原理从开始到放弃》,而本篇是介绍另外一个高频出现在并发场景的类ReentrantLock,有必要深入理解它的使用和原理。
如果你不想看源码的话,直接看图示 + 总结就能摸清整体的流程了。
使用
ReentrantLock使用起来很简单,例如:
ReentrantLock lock = new ReenTrantLock;
...
try {
lock.lock(); // 加锁
//do something();
} finally {
lock.unlock(); // 解锁
}
ReentrantLock 使用起来很简单,甚至比synchronized更直观,直接对当前线程上锁、解锁,不止如此,ReentrantLock还有更多的功能:
- lock() 获得锁就返回true,不能的话一直等待获得锁
- tryLock() 尝试获取锁成功就返回true,不能就直接返回false,不会等待
- tryLock(long timeout,TimeUnit unit) 跟tryLock一样,尝试获取锁,如果超过该时间段还没获得锁,返回false
- lockInterruptibly 获取锁,跟lock不一样的地方是,过程中会检测是否中断(interrupt),若是会抛出异常
- 构造函数
public ReentrantLock(boolean fair)
可设置公平锁还是非公平锁
这里不再细说ReentrantLock的使用方法。
特性
- 可重入锁
- 可响应中断
- 可尝试加锁
- 限时等待尝试加锁
- 公平锁、非公平锁
- 与Condition信号量结合使用
原理
看下ReentrantLock的源码,其中有个重要的变量sync
,是一个继承AbstractQueuedSynchronizer (以下称为AQS) 的Sync抽象类,分别由FairSync、NonfairSync类实现,代表着公平和非公平策略。
可见,ReentrantLock的实现基本依靠AQS实现的,所以如果要理解ReentranLock,有必要学习AQS。
AQS(AbstractQueuedSynchronizer)
AQS 基本数据结构是一个FIFO的双向队列,每个结点Node存储线程和其他信息。队列的头部结点表示:该结点对应的线程已经处于执行状态,占用了资源;剩下的队列里的线程则被挂起等待唤醒。
接着上面的示意图,来看一下Node结点类:
AQS 有两种模式,一种是独占模式 EXCLUSIVE ,另外一种是共享模式 SHARED;而ReentrantLock是独占模式。
SHARED 共享模式,表示可以多个线程同时执行
EXCLUSIVE 独占模式,表示只有一个线程能执行
CANCELLED:1 取消状态,表示这个结点被取消了,可能是被主动取消或者超时,后续这个结点会被踢出队列
SIGNAL: -1 通知后继结点,表示这个结点执行完成以后,需要通知唤醒后继的结点
CONDITION:-2 说明这个结点因为被某一个condition挂起了
PROPAGATE:-3 在共享模式下,下一次获取锁后可以无限传播(不太懂这个意思,后续再好好理解)
除了以上常量,再看下几个变量
waitStatus 当前结点的状态,默认是0,可以是CANCELL、SIGNAL 等
prev 前继结点
next 后继结点
thread 对应的线程
nextWaiter 下一个等待condition的结点
state 状态;是一个int值,表示当前线程占用资源的数量;0表示空闲,没有线程占用;ReentrantLock的state表示线程重入锁的次数
在AQS里面的方法分成两类:
- acquire()、acquireInterruptibly()、release() 不以share结尾的方法名;
- acquireShared()、acquireSharedInterruptibly()、releaseShared() 以share结尾的方法名;
顾名思义, 以share结尾的方法用于共享模式,不以share结尾的方法用于独占模式。
有了以上的了解,可以开始分析源码了
ReentrantLock 的抽象类Sync有两个实现: FairSync (公平策略) 、NonfairSync(非公平策略)
1.lock()
ReentrantLock 的 lock 在FairSync和NonfairSync的方式有一些不同
//ReentrantLock
public void lock() {
sync.lock();
}
以下分成两种NonfairSync.lock 和 FairSync.lock:
(1) NonfairSync.lock
// NonfairSync
final void lock() {
if (compareAndSetState(0, 1)) // 锁状态空闲时,尝试直接更新
setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程为独占锁的拥有者
else // 若失败,走正常流程获取锁
acquire(1);
}
}
compareAndSetState()
就是CAS尝试更新state字段
在ReentrantLock中state字段表示当前线程重入锁的次数,当state为0时候,表示锁是空闲的。
compareAndSetState(0, 1)
表示当state是0,直接更新为1,即空闲时候,直接CAS上锁;线程如果更新成功了,setExclusiveOwnerThread()
设置当前线程为锁的占有者;
如果失败了,走acquire()
获取锁。
// AQS.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS中已经实现了FIFO队列的各种操作,子类只需要重写tryAcquire()
和tryRelease()
两个方法;
tryAcquire()
尝试获取锁,在NonfairSync中实现,失败就addWaiter()
新生成一个wait结点,并加入等待队列;
acquireQueued()
表示在加入队列以后,阻塞挂起,被唤醒后再次获取资源,获取失败再次阻塞;
要是失败了就只能selfInterrupt()
中断了。
// NonfairSync
protected final boolean tryAcquire(int acquires) {
// 直接调用AQS的nonfairTryAcquire
return nonfairTryAcquire(acquires);
}
// AQS.java
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {