前言
前边两节我们分别讲了AQS的阻塞队列和条件队列,算是对AQS有了一个基本的了解,这一节中,来看一哈AQS在JUC中的一些锁上的应用
ReentrantLock
可重入锁,顾名思义,获得这个锁的线程还可以再次获得,与此对应的,放弃这个锁的次数必须跟获得次数对应,不然就不算彻底放弃。Synchronized也是可重入锁。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 当前持有锁的线程
private transient Thread exclusiveOwnerThread;
abstract static class Sync extends AbstractQueuedSynchronizer {
// some methods
}
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
复制代码
锁一般都会继承Lock接口,接口中主要定义一些锁的操作方法,lock() 之类的,我们不细说。 可以看到其实ReentrantLock有一个内部类属性sync,Sync继承自AQS.后边我们会看到大多数锁的方法都是基于这个内部类实现的。
ReentrantLock分为公平锁和非公平锁两种,均是Sync的子类
获取锁
public void lock() {
sync.lock();
}
复制代码
正如我们上边所说,通过内部类sync获得锁,sync又分为FairSync和NonfairSync.
非公平获取锁
static final class NonfairSync extends Sync {
final void lock() {
// CAS设置state为1
if (compareAndSetState(0, 1))
// 设置该锁持有者为当前线程
setExclusiveOwnerThread(Thread.currentThread());
// CAS失败
else
acquire(1);
}
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
复制代码
可以看到,若是CAS设置state为1成功就直接完了,否则的话acquire(1),可以看到这里的参数为1,参数指的其实就是更新的state的值,其实state在ReentrantLock中的语义就是重入次数。这里获取到锁的话,重入次数加1,所以这里的参数为1.
// 参数为 1
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
这个代码熟悉吗,又跳回到第一节中的AQS中来了,正是独占获取资源,我们之前说过AQS不提供tryAcquire(),所以tryAcquire()又会回ReentrantLock,同时,不要忘了我们这里是非公平锁,我们去看非公平锁的tryAcquire()
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// c==0说明当前没有线程持有锁
if (c == 0) {
// cas成功的话
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// c!=0且当前线程是锁持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 更新state,此时当前线程持有锁,不需要CAS
setState(nextc);
return true;
}
// 没有获得锁,放入阻塞队列
return false;
}
}
复制代码
总结下非公平锁的获取流程:
- CAS设置state由0到1,0的语义即锁空闲,CAS成功的话直接获得锁结束流程
- CAS失败的话,进入nonfairTryAcquire
- 若是state为0,再次尝试CAS,成功代表获取到了锁,将锁的持有者设为当前线程,返回true结束流程
- 若是state不为0,首先判断当前线程是否是锁的持有者,若是的话直接再次获取,直接更新state,返回true
- 走到这一步,当前线程只能进入阻塞队列了,后续流程参考第一节在阻塞队列中怎么获取锁
公平获取锁
所谓公平就是按顺序先来后到获取锁,比如A先来获取锁且A前边没有别的线程来获取锁,即便此刻轮不到A,下一个锁的持有者也必须是A
final void lock() {
acquire(1);
}
复制代码
可以看到这里相比非公平没有上来就CAS,上来就CAS没法保障A上来的时候state为0可以CAS成功,假如A之后又来个线程B请求锁,碰巧此时state为0,B就CAS成功了,而B在A之后,所以上来就CAS不能保证公平。
接着还是一样的去AQS中调用acquire,再回ReentrantLock调用公平锁的tryAcquire()
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// state为0
if (c == 0) {
// 如果当前线程是第一个来请求锁的且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;
}
复制代码
跟非公平相比,只多了一个判定条件,hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// h==t说明队列为空
return h != t &&
// h!=t并且s==null说明会有元素作为AQS第一个节点入队列
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
这是AQS中的一个方法,如果当前AQS队列为空或者当前线程是AQS的第一个节点(非哨兵节点)返回false。
其他方法获取锁
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))
// AQS中的方法
doAcquireInterruptibly(arg);
}
复制代码
这个方法可以响应中断
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
复制代码
这个方法不会阻塞,成功获取锁就直接返回true,否则返回false,可以看到采用的是非公平方式。
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
复制代码
带有超时时间的获取锁
释放锁
public void unlock() {
// 参数依然为1
sync.release(1);
}
复制代码
类似于上边的获得锁,参数依然为1,毕竟每释放一次重入次数减一,调用AQS的release,接着回调到ReentrantLock的tryRelease
protected final boolean tryRelease(int releases) {
// state-=1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
// c为0的话当前线程不再持有锁
setExclusiveOwnerThread(null);
}
// c不为0更新state,设置重入次数为原次数减一
setState(c);
// c!=0的话,返回失败,即并没有释放锁,只是冲入次数减一
return free;
}
复制代码
这个代码本身很简单,不过这里可以提一下前边一个埋点,第二节中有个fullyRelease方法还有印象不,这个方法中先得到state,然后全部release掉,即state清空,这也是我们说的await必须要释放锁,必须清空state
总结
ReentrantLock算是比较简单的,内部使用AQS实现的可重入锁,state指的是可重入次数,state为0表示当前锁空闲,state大于0说明锁被占用,同时还有非公平和公平获取锁两种方式,同一时刻只有一个线程持有该锁,从方法中也可以看出来,调用的都是AQS中的独占的方式。
个人公众号: