ReentrantLock获取锁、释放锁源码浅析

JUC包下的ReentrantLock是基于Aqs模板实现的,它区分公平锁和非公平锁,内部实现了两个同步器,本文关注非公平锁部分。

伪代码

我们先看两个伪代码:

1、获取锁

 1 if(获取锁成功) {
 2     return
 3 } else {
 4     加入等待队列
 5     for(死循环) {
 6         if (获取到锁) {
 7             return
 8         }
 9         阻塞线程
10     }
11 }

我们看到,如果一次获取成功则结束,如果没有获取成功将进入循环中,并且当前线程阻塞直到被唤醒并且获取到锁才结束。

2、释放锁

1 if(释放锁) {
2     唤醒等待队列中阻塞的首个线程
3 }

释放锁的逻辑比较简单,如果当前锁释放了,唤醒下一个。

DEMO

通过伪代码了解了ReentrantLock基本原理以后,我们从一个DEMO入手,看看它的源码实现

获取锁

DEMO中初始化了一个ReentrantLock实例,并且调用了lock()方法,在结束的时候调用了unlock()方法。我们先进入lock()方法看看互斥锁是怎么加锁的

ReentrantLock内部调用了一个sync对象的lock()方法,我们看看sync是什么

这里Sync是一个继承了Aqs模板的同步器抽象类,Sync有两个实现类,一个是非公平锁实现

一个是公平锁实现

好吧,简单来说就是ReentrantLock在初始化的时候构造了一个NonfairSync或者FairSync对象。并且在调用lock()方法的时候,其实是去调用这个Sync实例对象的lock()方法而实现的加锁操作。那么我们就看看Sync实例是怎么实现加锁的,进入NonfairSync的lock方法。

lock()方法先取执行了一个CAS操作,把一个state变量从0设置为1,state变量是声明在Aqs模板的成员变量

ReentrantLock在设计的时候认为state > 0的时候表示锁被持有,state = 0的时候表示锁没有持有者。所以这里的cas机制做了一次尝试获取锁的操作。如果获取成功了,那么就将当前线程设置为锁的持有者。如果设置失败了,意味着当前有持有者,那么调用Aqs的acquire方法去获取,我们看看Aqs的acquire方法做了什么

acquire方法,先尝试tryAcquire获取一次锁,如果获取成功则结束,我们看看NonfairSync怎么实现tryAcquire的

逻辑也很简单,采用CAS做了一次设置,如果是重入的话,那么state + 1。

如果失败先调用addWaiter方法,再调用acquireQueued方法。我们先看看addWaiter做了什么

我们看到,Thread被包装成了一个Node节点,Aqs中是使用链表的方式来实现等待队列的,如

总之,当前节点将会被添加到队列的尾巴,如果没有添加成功调用enq()方法,我们看看enq方法

enq方法其实就是通过自旋操作保证添加到队列的尾巴,所以addWaiter的核心就是没有获取到锁的线程被加入到队列中。我们再回到acquire方法中看看acquireQueued在addWaiter做了什么

acquireQueued方法的逻辑比较简单,其实就是在一个for循环中自旋,如果轮到当前节点了,那么就跳出循环。否则被阻塞,直接被唤醒重新自旋。

以上获取锁的源码,与一开始的伪代码逻辑是一样的,如果获取到锁那么state设置为1,重入再+1。如果获取锁失败,那么Thread被包装成Node加入到链表的最尾巴。并且在for循环里面判断是否轮到当前Node获取锁了。

释放锁

下面再看看ReentrantLock的unlock方法

跟lock一样,直接调用了Sync同步器的release方法

释放的逻辑比较简单,先调用了一次tryRelease,如果成功的话将等待队列里面的下个线程唤醒就结束了。我们看看NonfairSync是怎么实现tryRelease的吧

其实就是把state扣减,如果等于0的话,意味着完全释放锁,那么把独占锁的线程设置为null即可。

总结

从ReentrantLock中,我们看到它基于Aqs实现了Sync同步器,而同步器基本上只是实现了一个tryAcquire方法和tryRelease方法,他们分别会被Aqs中的acquire方法和release方法调用。其它的逻辑,比如入队出队全都被Aqs自己实现了。同时我们还看到了Aqs中多出使用CAS机制来控制数据的更改。另外Aqs提供一个state变量给我们,我们至于怎么使用它需要我们自己设计,比如ReentrantLock将state > 0设计为获取锁,state = 0设计为没有任何获取锁的线程。

原文地址:https://www.cnblogs.com/lay2017/p/11037516.html

时间: 2024-08-26 13:03:41

ReentrantLock获取锁、释放锁源码浅析的相关文章

ReentrantLock获取、释放锁的过程

看了篇文章,觉得分析得很透彻,其后总结的很到位,地址:http://www.iteye.com/topic/1083832 把获取与释放操作串在一起在简单看一下: 获取锁的时候将当前线程放入同步队列,并且将前一个节点的状态置为signal状态,然后阻塞 当这个节点的前一个节点成功获取到锁,前一个节点就成了整个同步队列的head. 当前一个节点释放锁的时候,它就唤醒当前线程的这个节点,然后当前线程的节点就可以成功获取到锁了 这个时候它就到整个队列的头部了,然后release操作的时候又可以唤醒下一

仿iphone动态萤火虫锁屏应用源码

该源码是仿iphone动态萤火虫锁屏应用源码,源码SkyLock,这也是最近弄了一款锁屏,苦于市场百般阻拦与锁屏应用数量实在太多,于是将它拿出来开源:废话不多说,希望大家能够希望,更多说明请看下面的吧. 详细源码下载:http://code.662p.com/view/9686.html <ignore_js_op><ignore_js_op> 后面的萤火虫是会动的,gif图片不会截取 <ignore_js_op> 20141202005325.png (106.46

Android应用Loaders全面详解及源码浅析

1 背景 在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现.同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader.它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下: 提供异步加载数据机制: 对数据源变化进行监听,实时更新数据: 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据: 适用于任何Activit

ArrayBlockingQueue 1.8 源码浅析

[TOC] ArrayBlockingQueue 1.8 源码浅析 一,简介 ArrayBlockingQueue 是一个用数组实现的有界队列:此队列按照先进先出(FIFO)的规则对元素进行排序:默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序的访问队列,即先阻塞的线程先访问队列:非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问:为了保证公平性,通常会降低吞吐量. 二,类UML图 三,基本

ReactiveCocoa2 源码浅析

ReactiveCocoa2 源码浅析 标签(空格分隔): ReactiveCocoa iOS Objective-C ? 开车不需要知道离合器是怎么工作的,但如果知道离合器原理,那么车子可以开得更平稳. ReactiveCocoa 是一个重型的 FRP 框架,内容十分丰富,它使用了大量内建的 block,这使得其有强大的功能的同时,内部源码也比较复杂.本文研究的版本是2.4.4,小版本间的差别不是太大,无需担心此问题. 这里只探究其核心 RACSignal 源码及其相关部分.本文不会详细解释里

java并发:jdk1.8中ConcurrentHashMap源码浅析

ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1.7,在jdk1.8中,ConcurrentHashMap主要使用了CAS(compareAndSwap).volatile.synchronized锁. 跟jdk1.8中的HashMap一样,数据结构是数组+链表+红黑树.当链表长度过长时,会转变为红黑树. jdk1.8的HashMap源码浅析,见

ConcurrentLinkedQueue 1.8 源码浅析

[TOC] ConcurrentLinkedQueue 1.8 源码浅析 一,简介 ConcurrentlinkedQueue 还是一个基于链表的,×××的,线程安全的单端队列,它采用先进先出(FIFO)的规则对节点进行排序,当我们加入一个元素时,它会插入队列的尾部,当我们获取元素时,会从队列的首部获取元素.它没有使用锁来保证线程安全,使用的是"wait-free"算法来保证整个队列的线程安全. 二,基本成员简介 Node 节点对象 // 存储的数据 volatile E item;

【Spark Core】任务执行机制和Task源码浅析2

引言 上一小节<任务执行机制和Task源码浅析1>介绍了Executor的注册过程. 这一小节,我将从Executor端,就接收LaunchTask消息之后Executor的执行任务过程进行介绍. 1. Executor的launchTasks函数 DriverActor提交任务,发送LaunchTask指令给CoarseGrainedExecutorBackend,接收到指令之后,让它内部的executor来发起任务,即调用空闲的executor的launchTask函数. 下面是Coars

Android应用ViewDragHelper详解及部分源码浅析

[工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 很久没有更新博客了,忙里偷闲产出一篇.写这片文章主要是去年项目中的一个需求,当时三下五除二的将其实现了,但是源码的阅读却一直扔在那迟迟没有时间理会,现在拣起来看看吧,否则心里一直不踏实. 关于啥是ViewDragHelper,这里不再解释,官方下面这个解释已经很牛逼了,如下: /** * ViewDragHelper is a utility class for

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<