明天咱们来学习可重入排他锁,它同样是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同步器(二)