ReentrantLock 是JDK 1.5开始提供的一种可重入的互斥锁,并且构造方法支持公平性参数。
ReentrantLock实现了Lock接口:
public class ReentrantLock implements Lock, java.io.Serializable {
...
}
Lock接口中定义了6个方法,需要自己去实现:
public interface Lock {
// 获得锁
void lock();
// 可被中断的获得锁
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁(如果可用),并立即返回值true。如果锁不可用,则此方法将立即返回值false
boolean tryLock();
// 如果锁可用,此方法将立即返回值true,如果锁不可用,则当前线程将处于休眠状态
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 解锁
void unlock();
// 条件锁
Condition newCondition();
}
ReentrantLock 有3个重要的内部类,分别是 Sync、NonfairSync、FairSync;
Sync 是后面两个的父类,继承至AbstractQueuedSynchronizer
NonfairSync和FairSync都继承至Sync
NonfairSync 主要用于实现非公平锁,FairSync 主要用于实现公平锁
ReentrantLock 就一个属性,就是sync
,在构造方法中初始化,通过构造方法参数决定使用公平锁还是非公平锁实现。
private final Sync sync;
无参构造方法构造非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
有参构造方法构造公平锁:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
获得锁的主要代码
public void lock() {
sync.lock();
}
从上面代码可以看出,锁的实现主要是在sync里面,而sync的实现有两个,分为公平和非公平锁,所以这里要分别看两种情况下不同的实现。
ReentrantLock lock = new ReentrantLock();
或 ReentrantLock lock = new ReentrantLock(true);
公平获得锁
sync.lock() 最终会调用FairSync.lock()里面的实现,FairSync中获得锁的对应源码如下:
static final class FairSync extends Sync {
// 以公平的方式锁
final void lock() {
// 调用AQS框架的逻辑
acquire(1);
}
// AQS acquire 方法会调用tryAcquire这个方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
// 成功获得锁,设置锁的所有者为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 可重入锁的逻辑
int nextc = c + acquires;
if (nextc 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
其中lock()方法中的acquire(1)方法会调用AQS框架中的实现,AQS框架中的acquire(int)方法是被final修饰的,不能被继承修改,这个方法会继续调用FairSync.tryAcquire()方法。
AQS.acquire() 方法实现如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire() 的逻辑可以总结为:
尝试获得锁,也就是调用tryAcquire()
方法,成功原子修改state字段标识成功获得锁。
如果没有获得锁,尝试将当前线程加入到队列addWaiter(node)
再次尝试获得锁acquireQueued(node,arg)
acquire() 里面的逻辑只有tryAcquire
是在 ReentrantLock 中实现的,其他像addWaiter、acquireQueued的分析请看关于AQS的分析文章
刚刚说了,tryAcquire(int)的逻辑实际上就是修改state字段,修改成功就是获得锁
分析上面tryAcquire(int)
源码,总结主要逻辑有如下过程:
获取当前线程
获取当前state值
如果state为0,说明锁资源空闲,当前没有其他线程获得该锁,当前线程可以获得该锁,获得锁过程如下:
如果state不为0,说明锁资源已经被线程获取了,也有可能是当前线程自己获得了锁资源
如果锁的所有者是当前线程,则state值+1(不需要使用CAS方式修改,因为之前已经获得了锁,现在是重入,只需要计数+1),这也就是可重入锁的逻辑,成功获得锁,返回true
首先调用`hasQueuedPredecessors()`方法检查是否还有等待获取锁的时间更长的线程
没有更早的其他线程排队,就尝试调用CAS方法`compareAndSetState(0, acquires)`原子修改state值
CAS 原子修改成功,代表当前线程成功获得锁,之后调用`setExclusiveOwnerThread(current);`设置获得锁的所有者为当前线程
成功获得锁,返回true
没有获得锁,返回false
之后就是AQS里面的逻辑了,排队、阻塞、等待唤醒获取锁等过程,过程请看之前关于AQS的分析文章
非公平获得锁
非公平获得锁时,sync.lock() 最终会调用NonfairSync.lock()里面的实现,NonfairSync的源码如下:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 非公平获得锁,进来直接使用CAS修改,修改成功就是获得锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// acquire 是AQS里面的方法,最终会调用到非公平锁的实现方法tryAcquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 非公平获得锁
return nonfairTryAcquire(acquires);
}
}
源码分析:
进来直接尝试CAS修改state值,如果修改成功,代表获得锁,然后设置获得锁的所有者为当前线程
CAS修改state失败,调用 acquire(1) 逻辑,最终会调用nonfairTryAcquire(acquires)
nonfairTryAcquire(acquires)
的逻辑是在Sync
里面的,主要实现源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
...
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// state 状态
int c = getState();
if (c == 0) {
// CAS 修改state 值
if (compareAndSetState(0, acquires)) {
// 成功获得锁,修改锁的所有者线程
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
// 检查锁的所有者是否是当前线程
// 当前线程获得锁,可重入逻辑
int nextc = c + acquires;
if (nextc 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
...
}
源码分析:
获取当前线程,当前 state 值
如果 state 值为0 ,直接使用CAS 修改,修改成功代表获得锁,如果成功获得锁,修改锁的所有者线程为当前线程
如果 state 值不为0 ,检查锁的所有者是否是当前线程,如果是,进入可重入逻辑,state值+1 ,成功获得锁
没有获得锁,返回false
通过比较公平获得锁和非公平获得锁的实现逻辑,可以发现他们的主要区别如下:
非公平获得锁进入时,直接使用CAS尝试获得锁
公平获得锁时,CAS尝试获得锁之前会检查是否还有等待获取锁的时间更长的线程,也就是hasQueuedPredecessors()
的 逻辑。
获得锁,除非当前线程被中断。该方法获得的锁也是支持可重入的锁,与lock()方法获得锁的区别就在于该方法获得锁时被中断会抛出InterruptedException异常。
private void doAcquireInterruptibly(int arg)throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 自旋
for (;;) {
final Node p = node.predecessor();
// 前驱节点是头结点才尝试获得锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
// 这个API支持被中断,所以抛出了异常
// 像lock() 等方法在这里是不会抛出异常的,只是标识了下被中断
throw new InterruptedException();
}
} finally {
if (failed)
// 被取消的节点
cancelAcquire(node);
}
}
尝试获取锁(如果可用),并立即返回值true。如果锁不可用,则此方法将立即返回值false。
源码如下:
public boolean tryLock() {
// 直接调用的和非公平方式获得锁tryAcquire一样的逻辑,没有获得锁会立即返回false
return sync.nonfairTryAcquire(1);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
...
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
...
}
从上面源码可以看出,tryLock()方法是直接调用的Sync.nonfairTryAcquire(int) 方法,该方法是NonfairSync.tryAcquire()方法的默认实现,具体的分析见上面非公平获得锁的分析;所以就算你在ReentrantLock 构造方法传入true,tryLock()
还是以非公平的方式获得锁。
如果锁可用,此方法将立即返回值true,如果锁不可用,则当前线程将处于休眠状态,等待指定的时间内获得锁返回true,否则返回false。
获得锁的过程中,当前线程被中断,会抛出InterruptedException异常。
源码如下:
public boolean tryLock(long timeout, TimeUnit unit)throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
AQS代码:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {
if (Thread.interrupted())
// 当前线程被中断,抛出异常
throw new InterruptedException();
// 尝试获得锁 || 等待指定时间获得不断尝试获得锁
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)throws InterruptedException {
if (nanosTimeout <&#61; 0L)
return false;
// 计算获得锁的最后期限时间
final long deadline &#61; System.nanoTime() &#43; nanosTimeout;
// 当前节点入队列
final Node node &#61; addWaiter(Node.EXCLUSIVE);
boolean failed &#61; true;
try {
// 自旋
for (;;) {
final Node p &#61; node.predecessor();
// 当前节点的前驱节点是头结点 才去尝试获得锁
if (p &#61;&#61; head && tryAcquire(arg)) {
// 获得锁&#xff0c;设置新的头结点
setHead(node);
p.next &#61; null; // help GC
failed &#61; false;
return true;
}
nanosTimeout &#61; deadline - System.nanoTime();
if (nanosTimeout <&#61; 0L)
// 判断是否已经过了最后期限时间&#xff0c;没有获得锁&#xff0c;直接返回false
return false;
// 判断是否要阻塞&#xff0c;spinForTimeoutThreshold &#61; 1000 &#xff0c;相当于允许1000纳秒的误差
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
// 继续阻塞线程
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
// 被中断了&#xff0c;抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
源码分析&#xff1a;
当前线程如果被中断&#xff0c;则立马抛出异常
尝试获得锁&#xff0c;如果成功获得锁&#xff0c;立即返回true
以当前线程为节点&#xff0c;加入到等待队列
再次尝试获得锁&#xff0c;如果成功获得锁&#xff0c;立即返回true
判断是否已经过了最后期限时间&#xff0c;如果过了期限时间&#xff0c;立即返回false
调用LockSupport.parkNanos(this, nanosTimeout);
阻塞线程&#xff0c;实际上也就是调用Unsafe类的park
当前线程如果被中断&#xff0c;则立马抛出异常
自旋&#xff0c;等待线程被唤醒&#xff0c;重复步骤4~7
注意&#xff1a;从上面源码final Node p &#61; node.predecessor();p &#61;&#61; head && tryAcquire(arg)
可以看出&#xff0c;在公平模式下&#xff0c;只要有其他更早的线程在排队还没有获得锁&#xff0c;该线程就不可能立马获得锁
释放锁&#xff0c;如果当前线程是锁的持有者&#xff0c;则state减一&#xff0c;如果state为0&#xff0c;则锁被释放。
源码如下&#xff1a;
// ReentrantLock 代码&#xff1a;
public void unlock() {
sync.release(1);
}
// AQS 代码&#xff1a;
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h &#61; head;
if (h !&#61; null && h.waitStatus !&#61; 0)
// 如果存在&#xff0c;唤醒下一个节点线程
unparkSuccessor(h);
return true;
}
return false;
}
// ReentrantLock 内部类 Sync代码&#xff1a;
protected final boolean tryRelease(int releases) {
int c &#61; getState() - releases;
if (Thread.currentThread() !&#61; getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free &#61; false;
if (c &#61;&#61; 0) {
free &#61; true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
源码分析&#xff1a;
如果当前线程是锁的持有者&#xff0c;则state减一
state减为0&#xff0c;表示锁成功释放&#xff0c;设置锁的持有者线程为null
如果锁释放成功&#xff0c;如果队列中有等待的线程&#xff0c;唤醒下一个线程获取锁
篇幅有限&#xff0c;条件锁在另一篇文章分析。
ReentrantLock 可以实现公平锁和非公平锁&#xff0c;默认是非公平模式
公平锁和非公平锁的主要区别是&#xff1a;非公平锁在刚获取锁的时候会直接尝试一次CAS修改同步状态&#xff0c;不会管队列中是否有排队等待锁的线程&#xff0c;修改成功就获得锁&#xff1b;公平锁就相反&#xff0c;没有直接CAS修改这一步&#xff0c;而是要去检查队列中是否有更早在排队的线程。
ReentrantLock 只完成加锁(可重入)和解锁的过程&#xff0c;其他功能如排队入队&#xff0c;阻塞&#xff0c;唤醒下一个线程&#xff0c;中断异常等都是在AQS里面实现的
版权声明&#xff1a;本公众号所有文章除特别声明外&#xff0c;转载请注明出处&#xff01;