明天咱们来学习可重入排他锁,它同样是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 我什么时候能达到这个水平啊)。

顺带提几句:

  1. 尽管这个办法能够很不便的实现生产者/消费者模型,然而java JUC包下其实曾经有一个类(就是应用了ReentrantLock的condition)实现其性能了:LinkedBlockingQueue类(嘻嘻,没想到吧。)
  2. 在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同步器(二)