前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ReentrantLock源码解析

ReentrantLock源码解析

作者头像
写一点笔记
发布2020-08-25 14:20:51
5460
发布2020-08-25 14:20:51
举报
文章被收录于专栏:程序员备忘录程序员备忘录

在java编程中,经常需要多代码进行加锁来防止多线程可能引起的数据不一致。而锁的类型有公平锁和非公平锁。公平锁的意义就是按照顺序,而非公平锁则是相反的。也就是说非公平锁会让参与资源竞争的线程都具有获取资源的概率,而公平锁则是谁先进入队列谁就具有获取锁的概率。而越是队列靠后的线程越是没有提前使用资源的权利。在JUC工具包中,多线程的锁机制其实就是基于队列实现的,其主要就是先进先出队列实现的(FIFO)。

我们还是通过构造方法的静态方法等类的初始化顺序进行逐步阅读源码。

初步查看ReentrantLock,我们发现其中有抽象类Sync,已经Sync的两种不同实现方式,其中NonfairSync是非公平锁,FairSync是公平锁,Sync继承了AbstractQueuedSynchronizer。而AbstractQueuedSynchronizer就是传说中的AQS,此处我们可以将AQS理解为专门用来维护FIFO队列的同步器。它提供了很多方法,能够提供多线程情况下的节点的安全操作。

在ReetrantLock的构造函数中,我们发现默认采用的非公平锁,在使用的时候可以通过条件变量fair决定采用那种锁。

我们在代码中经常会使用的Lock方法其实也是直接调用Sync类的子类中的方法。我们使用公平锁FairSync近追踪。

发现方法已经进入了Sync类的父类AQS,果然AQS才是最后的大BOSS,本来打算绕过去的,看来所有的一切都还是要面对啊。

这里边有三个方法

代码语言:javascript
复制
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

通过字面理解,trayAcquire就是试图获取。通过之前的lock的追踪,那么这个方法就是获取锁的意思。acquireQueued就是在获取锁失败之后才进行,而传入的参数为AddWaiter方法,AddWaiter的参数又是Node.EXCLUSIVE,exclusive是排斥的意思,通过前边的解释,addWaiter函数的基本作用就是将当前线程添加到队列里。如果没有获取锁就将其添加到队列中,那么acquireQueued方法是做什么的?现在还不得而知,selfInterrupt方法按照字母理解就是中断当前线程。

分析到这里的时候我觉得有必要重新理一理对多线程的理解。我们看到这里并没有传入线程,那是因为我们可以通过Thread.currentThread的方法获取。当单个线程阻塞了的时候我们可以唤醒它并让其进行运行。所以说我们的AQS其实并不是我理解的一种专门针对后边JUC框架的工具类,而是具有极大兼容性的。只要我们继承了AbstractQueuedSynchronizer,并去操作其提供的保护方法,便可以实现高并发条件下的代码块同步。是的AbstractQueuedSynchronizer将复杂的队列操作已经帮我们做好了。

但是当我们追踪到tryAcquire方法的时候发现AQS啥都没有做???不是说好的么,AQS帮我们搞定一切,我们只需要调用即可啦,怎么可以这样。这让人情何以堪。好吧,既然父类没有搞定,那么这里的逻辑应该写在哪里呐?那当然是在子类中了,也就是FairSycn和NoFairSycn进行了重写。那么这块怎么理解?那当然就是两个兄弟需要进行不同的实现了。就是差异化了。我们学java的时候就知道,一般重写父类方法之后,子类的实体会直接调子类已经重写的新方法,而调用父类的方法则需要super的调用方法,是的,大佬的java基础很强大。。

继续我们分析的事业,依旧按照FairSync的子类进行寻找。

发现首先进行了getStatue的操作,而getStaue是在AQS中,根据解释,该变量就是用来表示同步状态的。那么同步是什么意思?而且这个是volative的,于是,又陷入了无边的迷惑之中。但是我们知道volative是直接操作主存的,而且当这个变量为0的时候就直接调用了compareAndSetState(0,acquires);

但是这个方法比较好理解,这里采用了CAS进行了原子操作,先将state的值与0进行比较,如果相等就将其值变为acquires,否则就是一顿自旋!!!

而且在设置成功之后还setExclusiveOwnerThread,看来是进行设置当前线程为独占和排它的意思。但是排它了之后呐?我们在添加锁的时候,一般都是先lock,然后再unlock,中间进行代码运行。当其他线程进来的时候,根据上述分析,应该都是加入的AQS队列中,如果AQS中没有排队的线程,那么就直接设置当前线程为独占?????这有什么意思。不就是我一个么,为啥还得搞个独占的?我走还不行吗?但是仔细思考之后,还是觉得现实生活和代码差别太大,这种感觉就像万有引力和广义相对论一般,为了安全期间还是设置一下独占,就是为了防止因为没有标识而导致所以需要线程都直接run的格局吧,那么咱们的AQS还有意义么?答案肯定是没有了。

这里需要注意一下的是设置独占模式的代码直接写在了abstractOwnableSynchronizer在里,是不是感慨大佬就是大佬。佩服佩服

我们继续我们的事业

既然上边的队列显示没有其他线程,那么我们的线程就是成功了,然后可以直接按照代码往下跑了。但是请看下边这个else if,如果当前是独占线程,那么就将同步变量进相加,然后setSate,那么这里的setState一定是CAS的。但是你有没有发现有点不科学,既然getState不等于0,也就是已经有独占的线程了,但是这个独占的线程还是我自己。。就需要把state进行累加。那么这里就是说代码如果发生了类似循环的模式,当前线程还是持有锁的意思。是的,这就是“可重入锁”。显然对于其他线程来说如果没有独占锁,那么妥妥的进入AQS队列里排着。。。

通过上述分析,我们大概了解了tryAcquire的方法。那么接下来就是acqureQueued方法系。

这个方法还是比较容易理解的,大概的意思就是将当前线程进行封装,然后将其添加到队列的最后,并将这个线程节点进行返回,然后作为参数进行我当时感到迷惑的那个方法。感觉暴风雨即将来临啊!我们需要严阵以待

但是如果tail尾节点是空的,那么是不是说明我们的机会来了。所以enq方法大概就是做这个事情的。

看到没,如果尾节点为空,那么我当前的节点就是老大,否则也能排个老二

。如果这样的话,那么之前的让我感到迷惑的方法似乎不那么神秘了。感觉那么方法就是要我享受独占资源了,是不是都是自己人。呵呵呵

。但是咋不能凭空猜测,需要用事实考证。

好像跟上边的猜测有些出入,因为addwaite还有处于队尾的可能啊,好吧那么这个方法就是为了兼容这两种情况了,感觉就是进行二次确认的意思了。通过自旋拿到前置节点,然后判断其是否为头节点,因为头节点就是下一次资源的享受者,如果其前置节点为头结点,那么就尝试获取锁,如果成功了就头节点的线程已经从临界区走完,轮到我来表演了。那么当然先设置自己为老大,然后反回false,让之后的自我中断操作不在进行。

如果这种尝试并没有成功,也就是说我们还不在老二的位置,或者处于老三甚至老十三的位置。那么我应该进入阻塞队列吗?按理说这块应该结束啊,还尝试什么啊,既不是老二,还没有抢到头节点的位置,那就应该进入阻塞队列,并进行自我中断啊。搞不懂这块要做什么。。不过我们还是得仔细分析。

这块的代码似乎与上边的头节点抢占资源有点类似,如果其前置节点是Node.signal状态那么当前线程就可以中断了,因为之前至少有一个处于等待信号的状态。其中有一个do。。。while循环,大概就是跳过的意思。那么这里的ws>0应该不是一个好信号,估计是打算抛弃的线程节点。那么他们的ws在什么时候会大于0?看来当ws大于0就表示当前线程已经选择放弃竞争了...但是我还是比较迷惑,它为何会选择放弃。他放弃的边界条件是什么?

代码语言:javascript
复制
* Status field, taking on only the values:
*   SIGNAL:     The successor of this node is (or will soon be)
*               blocked (via park), so the current node must
*               unpark its successor when it releases or
*               cancels. To avoid races, acquire methods must
*               first indicate they need a signal,
*               then retry the atomic acquire, and then,
*               on failure, block.
*   CANCELLED:  This node is cancelled due to timeout or interrupt.
*               Nodes never leave this state. In particular,
*               a thread with cancelled node never again blocks.
*   CONDITION:  This node is currently on a condition queue.
*               It will not be used as a sync queue node
*               until transferred, at which time the status
*               will be set to 0. (Use of this value here has
*               nothing to do with the other uses of the
*               field, but simplifies mechanics.)
*   PROPAGATE:  A releaseShared should be propagated to other
*               nodes. This is set (for head node only) in
*               doReleaseShared to ensure propagation
*               continues, even if other operations have
*               since intervened.
*   0:          None of the above

如果ws不大于0,而且不等于-1,则将其设置为signal,并发挥false。对这块比较迷惑

最后的中断是最简单的操作了。

在分析完Lock之后,分析unlock就相对简单多了,无疑就是对其state状态进行反向操作而已。这里就不进行详细的解析了。有兴趣的朋友可以自行研究。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员备忘录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档