今天来说一说ReentrantLock类。ReentrantLock是可重入的独占锁,同时只能有一个线程获取该锁,其他获取该锁的线程就会被阻塞挂起。
ReentrantLock内部最为核心的就是Sync实例,内部类Sync继承AbstractQueuedSynchronizer,又根据公平锁还是非公平锁实现对应的FairSync类和NonfairSync类。ReentrantLock创建公平锁的方式:ReentrantLock lock =new ReentrantLock(true)。ReentrantLock创建非公平锁的方式:ReentrantLock lock =new ReentrantLock(false),参数不传,默认为非公平锁。
ReentrantLock通过操作AQS中的state状态值来实现可重入。默认情况,state为0,表示没有任何线程获取到锁。当一个线程获取该锁,通过CAS尝试将state值设置为1,如果CAS设置成功则该线程获取到锁,被记录该锁的持有者为该线程。当该线程再次获取该锁的时候,state值会被设置为2,从而实现了可重入。在该线程释放锁的时候,会尝试将state值减1,减1后状态值为0则当前线程释放该锁。
下面通过ReentrantLock内部的源码来进一步加深对ReentrantLock获取锁、释放锁的过程。
lock()
获取锁。
public void lock() {
sync.lock();
}
获取锁方法只简单调用了sync.lock()方法,通过公平锁和非公平锁会调用各自具体的实现。下面会对公平锁对应的FairSync类的实现和非公平锁对应的NonfairSync类的实现进行分别说明。
NonfairSync-lock()
final void lock() {
//(1)
if (compareAndSetState(0, 1))
//(2)
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//(3)
}
(1)通过CAS尝试将state状态值修改为1.
(2)通过CAS成功将state状态值修改为1则说明当前线程获取到锁,该锁记录当前线程。
(3)通过CAS失败,则会调用acquire方法,内部先调用tryAcquire方法,尝试获取资源,尝试获取失败则将当前线程插入到AQS阻塞队列中并阻塞挂起,具体的实现看“AQS源码剖析”这篇文章。
在“AQS源码剖析”这篇文章说到tryAcquire方法是根据上层的不同机制实现各自的tryAcquire方法,下面就来看看NonfairSync类是如何实现的?
NonfairSync-tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//(1)
if (c ==0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//(2)
int nextc = c + acquires;
if (nextc <0)// overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//(3)
return false;
}
(1)获取state状态值,如果等于0,说明当前锁没有线程获取,通过CAS尝试设置state,设置成功则将当前线程记录到锁内部。这里没有判断是否有比当前线程更早请求锁的线程,而是使用了抢夺策略。
(2)获取state状态值,如果不等于0,说明当前锁已经有线程获取,如果获取该锁的线程就是当前线程,则将state进行加一,实现可重入。
(3)(1)中CAS失败或(2)中锁记录的线程不是当前线程,则返回false,之后会添加到AQS阻塞队列中并阻塞挂起。
FairSync-lock()
final void lock() {
acquire(1);
}
FairSync类中的lock方法只是简单的调用了AQS的acquire方法,重点是在FairSync中实现的tryAcquire方法。
FairSync-tryAcquire(int acquires)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c ==0) {
//(1)
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;
}
}
(1)查看源码发现FairSync-tryAcquire(int acquires)方法和FairSync-tryAcquire(int acquires)方法的实现差不多,就是FairSync-tryAcquire(int acquires)方法多了一个hasQueuedPredecessors函数调用,该函数用来判断当前AQS阻塞队列中是否存在阻塞的Node,公平锁实现公平就是通过判断当前AQS阻塞队列中是否存在阻塞的Node,让先阻塞的Node先进行获取锁。
unlock()
释放锁。
public void unlock() {
sync.release(1);
}
释放锁方法只简单调用了sync.release(1)方法,公平锁和非公平锁的释放逻辑是一致的。
release(int arg)
public final boolean release(int arg) {
//(1)
if (tryRelease(arg)) {
Node h =head;
if (h !=null && h.waitStatus !=0)
//(2)
unparkSuccessor(h);
return true;
}
return false;
}
(1)尝试设置state值。
(2)设置state值成功之后,则在AQS阻塞队列从head往tail,唤醒第一个非CANCELLED状态的线程。
tryRelease(int releases)
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//(1)
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free =false;
//(2)
if (c ==0) {
free =true;
setExclusiveOwnerThread(null);
}
//(3)
setState(c);
return free;
}
(1)当前线程不等于锁记录的线程则会报IllegalMonitorStateException异常,释放锁会报IllegalMonitorStateException异常,是因为未获取锁的线程调用了unlock方法导致的。
(2)如果state减去releases值之后等于0,说明可以释放当前锁,将当前锁的记录线程设置为null。
(3)设置state值。
今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。