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

ReentrantLock的lock与unLock方法源码分析

作者头像
大猫的Java笔记
发布2020-09-30 01:35:56
4590
发布2020-09-30 01:35:56
举报

1.lock方法源码

.

前面已经说过了ReentrantLock的基本用法,下面我们通过源码对ReentrantLock进行分析,首先写一个测试类,作用是在debug的时候好进行源码分析;测试类代码如下,使用两个线程模拟加锁过程,若第一个线程拿到锁以后实际上第二个线程时拿不到的,没有unlock进行释放锁。

当我们进行debug进入到lock方法的时候,实际上我们可以看到调用的是NonfairSync的lock方法,而NonfairSync究竟是一个什么呢?

NonfairSync和FairSync都是一个ReentrantLock的一个内部类,NonfairSync是ReentrantLock的非公平锁的实现,而FairSync则是公平锁的实现;两个类都继承自Sync,而Sync又继承自AbstractQueuedSynchronizer(AQS),通过模板方法模式在NonfairSync或FairSync进行实现。

回到正题继续通过debug方式一条路摸到黑,再往下看的时候我们先来看一下state这个变量和exclusiveOwnerThread。

state用于记录现在锁是否已经被拿到了,如果被拿到则变为1,当线程进行重入1次后就会变成2,只有state为0的时候,其他线程才能获取到锁。使用volatile为了线程可见,当一个线程更改后另一个线程能够马上知道已经被其他线程占用了锁。

compareAndSetState(0, 1)是通过cas的方式进行改变state的状态,当从0改变成1的时候返回true,实际上就是尝试去看state有没有被线程占用,当第一个线程拿到锁后就会直接执行setExclusiveOwnerThread方法,;把当前线程赋值给exclusiveOwnerThread,即当前锁被那个线程占用的,若为false则已经被占用调用acquire方法;而使用cas的操作,原因是考虑到多线程情况下线程安全问题。

而qcquire方法中又调用了tryAcquire方法,这里是尝试获取,当然只有获取不成功的时候才会执行后面的qcquireQueued方法。tryAcquire中拿到当前的状态来判断如果是0,那么继续采用cas方式去更改state的状态,同时设置锁被当前线程占用,而如果不是0则说明已经被占用,那么此时查看当前线程和占用锁的线程是否同一个,如果同一个那么将state+1,也就是重入锁。而如果不是则返回false。

由于返回false,则会执行addWaiter,当进入addWaiter后你看到node时,点进去看到prev和next的两个参数时,这时候第一反应没错就是个双向链表,且这个双向链表存放的是当前线程;回到addWaiter方法中,拿到尾结点,判断尾部结点是否为空,如果不为空则把当前线程的结点的上一个变为尾部结点,尾部结点的下一个结点变为当前线程结点。再通过cas方式改变尾部结点为当前结点,当然是用cas还是因为多线程情况下线程安全问题。如果尾部结点为空则调用enq方法,而enq则是在死循环,直到尾部结点不为空的时候才能结束,当t==null时通过cas的方式进行初始化头尾结点,源码注释中也说明了必须进行初始化。直到t不等于null的时候,此时则把当前线程的结点的上一个结点指向之前的尾结点,同时cas改变尾部结点,最后把当之前尾部结点的下一个结点指向当前线程结点。实际上就是一个把当前线程放入到链表尾部的过程

acquireQueued源码,首先可以看到一个for的死循环,然后拿到当前结点的前置结点,然后判断当前结点是否为头结点,如果为前置结点继续尝试获取锁,如果获取到了锁则将当前结点设置为头结点(也就是头结点就是持有锁的线程)。

shouldParkAfterFailedAcquire源码,首先拿到上一个结点的状态,然后判断状态如果是后续线程需要释放则直接返回true,返回true后会进入到parkAndCheckInterrput方法调用LockSupport方法阻塞当前线程。而如果状态是大于0,则表示前面线程已经取消等待,此时要做的就是把当前线程放到没有取消等待的后面去。当然如果都不是则执行compareAndSrtWaitStatus的cas操作将上一个结点的状态修改后续结点都需要释放的状态。

到此ReentrantLock的lock方法源码结束,总结一下lock的整体流程。

2.unlock释放锁源码方法源码

.

开始之前依然写一个测试类好用于debug,上面的测试类和下面的测试类实际开发中必须放入try和catch中且unlock的代码必须放入到finally中,这样能够确保锁一定是会被释放的,放置其他线程永远拿不到锁。

调用Nonfair的release方法

release方法实现如下,通过tryRelease我们可以看出来这是一个尝试释放的方法,同时根据释放锁的结果来进行处理,如果释放成功则拿到头结点,然后看队列的头是否为空,即是否有等待线程存在,同时头结点不为0,此时调用unparksuccessor进行唤醒等待队列中的线程;下面我们先进tryRelease看看一下具体的实现方式。

tryRelease方法首先对state进行减1,然后看当前线程是否是加锁的线程,然后判断state是否为0,如果为0表示已经释放完毕即重入后的也释放掉了,此时将持有线程的exclusiveOwnerThread设置为空,即现在没有线程占有锁;若state不为0说明重入过且重入后并未释放完毕,此时将更改后的state的值重新赋给state。

unparkSuccessor源码,拿到头结点的状态,查看是否后续线程需要释放,即后续有等待线程也就是ws<0,那么此时因为头结点已经释放了锁,所以需要将头结点的状态由-1改回为0,源码中采用的也是cas操作。改完后判断是否为空或者大于0,也就是释放的线程是无效的,那么需要从后续节点中找到有效的节点,最后通过LockSupport.unpark来释放线程队列中的等待线程。

到此ReentrantLock的unlock方法源码结束,总结一下unlock的整体流程。

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

本文分享自 大猫的Java笔记 微信公众号,前往查看

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

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

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