ReentrantLock源码详解
ReentrantLock是Java中常用的锁工具,今天我们来了解一下。本篇文章设计到AQS,如果了解到不太清楚的话,可以看我之前写的文章AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Condition
ReentrantLock概述
ReentrantLock是基于我们之前讲过的AQS构造的。ReentrantLock是一个独占锁,提供了非公平锁和公平锁两种模式。
我们首先说下公平锁和非公平锁的区别:
公平锁:线程在请求锁的时候,依次入队,严格按照先来后到的顺序分配锁。
非公平锁:线程在请求锁的时候,如果锁可用,就能申请,申请不到才会入队。
通过构造参数传入true或false来控制是否公平锁,true为公平锁,空构造方法默认生成非公平锁。
public ReentrantLock(boolean fair) {
//FairSync对应着公平锁
//NonfairSync对应着公平锁
//二者都继承于Sync类,而Sync继承于AQS抽象类
sync = fair ? new FairSync() : new NonfairSync();
}
public ReentrantLock() {
sync = new NonfairSync();
}
我们来通过源码来分析公平锁和非公平锁的申请流程的区别:
公平锁:
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
//如果获得锁失败,就将线程入队,之前详细说过,不在说了。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//队列为空或者当前线程是head的后继节点,并且CAS申请锁成功
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;
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//判断队列非空并且当前线程不是head的后继节点,只有head的后继节点才能申请锁,head是个哨兵空节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从代码可以看出,只有当队列为空的时候,线程才会直接获得锁,其他情况就直接入队
非公平锁
final void lock() {
//不管三七二十一,上来就是申请锁。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获得不到锁,执行acquire
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//非公平锁
return nonfairTryAcquire(acquires);
}
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;
}
加锁方式
ReentrantLock提供了三种加锁方式
加锁方法 | 模式介绍 |
---|---|
tryLock() | 按照非公平锁申请,只请求一次,不会阻塞 |
tryLock(long timeout, TimeUnit unit) | 设置超时时间来申请锁,最多阻塞timeout个unit秒,可以响应中断 |
lockInterruptibly() | 阻塞式申请锁,抢锁的时候响应中断 |
lock() | 阻塞式的申请锁,抢锁的时候不响应中断 |
tryLock
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
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()只会试着抢一次锁,成功就成功,不成功就拉倒!
tryLock(long timeout, TimeUnit unit)
//TimeUnit说时间单位,timeout为数量
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
//通过抛出异常来响应中断
throw new InterruptedException();
//tryAcquire(arg)先申请一次,如果申请成功就直接返回
//申请不成功,就执行doAcquireNanos
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//如果时间小于0,返回false
if (nanosTimeout <= 0L)
return false;
//最后期限
final long deadline = System.nanoTime() + nanosTimeout;
//向锁queue中加入node
final Node node = addWaiter(Node.EXCLUSIVE);
//下面的代码之前我们在AQS文章中都讲过,忘记的可以温习一下。
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 true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
//如果剩余时间大于阈值的话,就将线程挂起一下
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
//挂起nanosTimeout时间,到时间如果没有被唤醒就自动苏醒
LockSupport.parkNanos(this, nanosTimeout);
//如果线程在抢锁期间中断了,就抛出异常
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
//出现异常后,node设置为cancel状态,放弃锁
if (failed)
cancelAcquire(node);
}
}
从上述代码看,tryLock(long timeout, TimeUnit unit)是在规定时间内申请锁,是响应中断的。具体做法是先会无脑申请一次锁,申请失败就会一直自旋CAS申请,如果剩余时间大于阈值,就将线程挂起,阻塞时间是剩余的时间。
lock()
public void lock() {
sync.lock();
}
lock就是我们之前说公平锁和非公平锁的时候列举的代码,会一直阻塞申请锁,在申请锁期间不会响应中断,抢锁成功后才会处理中断。
lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//如果线程被中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
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())
//当被中断后,就直接抛出异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从代码中可以看出,lockInterruptibly()是一直阻塞的申请锁,但是会响应中断
unlock()
public void unlock() {
sync.release(1);
}
release将锁释放,并且唤醒锁queue中的head的next节点来争夺锁。
这个我们之前在AQS中独占锁的释放中详细说过了,不再详细叙述了。
new Condition()
这个之前我们在AQS中也说过了,不在详细叙述,感兴趣的可以看文章开头的AQS的链接