关于java:AQS用配钥匙和保险箱理解可重入锁ReentrantLock

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理