明天咱们来学习可重入排他锁,它同样是 JUC 包下应用 AQS 同步框架来实现的,因而代码比拟简洁,只有理解了 AQS 的原理那么就会感觉 it is so easy。
1 怎么了解可重入锁
排他 ,阐明锁只能被一人占有(就相似信号量(semaphore)为 1,每一时刻只能有一人领有)。 可重入,同一个人能够屡次应用同一资源,这十分正当。试想一下,当一只保险箱的钥匙归你所属后,难道你不能再去锁匠那再配一把来开箱子吗?在这个法制社会里,钥匙是你的,那你去配钥匙必定是非法的,至于你为什么要配钥匙呢?一家人嘛,不过我置信你不可能把备份钥匙给他人用的吧,毕竟保险箱的货色可都是你们一家人,如果给了他人一份备用钥匙,那就不可设想喽。(这个和信号量有区别,信号量是每次应用都是须要获取凭证的,它可不论你们是不是一家人。不过其次要起因是两者关注点不同:信号量次要是关注的进入的量多少,而锁是为了爱护资源的 0 平安,而为了平安,须要怎么做呢?当然确保是一家人喽)。
2 可重入锁(ReentrantLock)的 condition
在剖析源码之前,咱们先简略来讲一下 condition。Java Lock 接口定义中有一个 newCondition()办法,返回 condition,而这个 condition 要实现的成果就相似于 java 中的 wait/notify 统一。也就是说这个 condition 的呈现其实就是为了代替它(因为应用这个常常出错)。这里咱们简略讲一下原理,有趣味的小伙伴能够自行看一下源码,这里不再赘述:因为它还是应用了 AQS 的框架(其外部放弃着一个同步队列,能够获取锁),那么为了实现期待和唤醒,它又减少了一个期待队列,当调用 await 办法时,节点退出到这个期待队列中。而当调用 signal 办法时,只有把节点从期待队列中拿进去再放到同步队列中,让其有机会去获取锁了,condition 这个实现逻辑十分的清晰,也十分简洁,可见功底非同一般(T_T 我什么时候能达到这个水平啊)。
顺带提几句:
- 尽管这个办法能够很不便的实现生产者 / 消费者模型,然而 java JUC 包下其实曾经有一个类(就是应用了 ReentrantLock 的 condition)实现其性能了:LinkedBlockingQueue 类(嘻嘻,没想到吧。)
- 在 AQS- 用团购的形式了解闭锁(CountDownLatch)这篇文章中咱们有提到过能实现相似的性能的 CyclicBarrier,其原理也用到了这个 condition。感兴趣的小伙伴们能够去看看其实现哦。
3 可重入锁(ReentrantLock)源码剖析
当初咱们来看看锁的源码吧。对于锁而言,咱们罕用的办法有 lock 和 unlock.。咱们以开保险箱的情景来形容一下其实现。
构造函数:
// 规定一下,拿钥匙的排队规定:是否须要严格排队。// 默认是 false(吞吐量更大,因为偏心锁带来更多上下文切换)。public ReentrantLock(boolean fair)
Fair:是否偏心。和之前讲的信号量等相似,如果平安,则人必须依照排队的程序来拿钥匙,否则前面来的人也有机会获取锁。
Lock 获取锁:(也可能是配锁):
public void lock() {sync.lock();
}
// 以非偏心为例, 偏心的逻辑与信号量实现统一 会先判断是否后面有人排队,final void lock() {if (compareAndSetState(0, 1)) // 尝试获取原装锁 setExclusiveOwnerThread(Thread.currentThread()); // 标记是谁获取锁的人
else
acquire(1); // 理论调用 tryAcquire 办法
}
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;
}
当人获取锁后呢,会记录下锁的拥有者。为什么要记录呢?当然是为了不便下一次这个家庭的人要应用保险箱,要拿钥匙须要去锁匠那里再配一把。具体分析一下代码:
if (compareAndSetState(0, 1)): 尝试获取原装锁。
setExclusiveOwnerThread(current): 记录获取钥匙的人,其实就是宝箱当初属于这个家庭了
else if (current == getExclusiveOwnerThread()): 是不是钥匙的所属人一个家庭的,是的话他能够从新配一把钥匙哦。同时之后要记录保险箱一共有多少把钥匙了(不便前面偿还,法制社会要保障保险箱相对平安,必须所有钥匙都偿还)。
锁的开释:(保险箱钥匙要传承给他人了,可是这么多钥匙都要还啊,不然谁晓得你会不会前面来偷开他人的宝箱呢)
public void unlock() {sync.release(1);
}
protected final boolean tryRelease(int releases) {int c = getState() - releases; // 开释了钥匙,还有几把在里面没有还
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 钥匙全还了
free = true;
setExclusiveOwnerThread(null); // 把宝箱所属人清空。}
setState(c);
return free;
}
开释的逻辑非常简单,就是把所有外借的钥匙包含备份钥匙都偿还了,那宝箱又成了无主之物。前面排队的人就又能够持续来获取钥匙了,同时也不必放心这个保险箱会有其备份钥匙在他人手上了。
- 举荐浏览 –
用火车购票的形式关上 AQS 同步器(一)
用火车购票的形式关上 AQS 同步器(二)