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

6次阅读

共计 2772 个字符,预计需要花费 7 分钟才能阅读完成。

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

正文完
 0