| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> ReentrantLock的实现原理 -> 正文阅读 |
|
[Java知识库]ReentrantLock的实现原理 |
ReentrantLock 简介ReentrantLock 实现了 Lock 接口,是一种可重入的独占锁。 相比于 synchronized 同步锁,ReentrantLock 更加灵活,拥有更加强大的功能,比如可以实现公平锁机制。 首先,先来了解一下什么是公平锁机制。 ReentrantLock 的公平锁机制我们知道,ReentrantLock 分为公平锁和非公平锁,可以通过构造方法来指定具体类型:
公平锁 在多个线程竞争获取锁时,公平锁倾向于将访问权授予等待时间最长的线程。 也就是说,公平锁相当于有一个线程等待队列,先进入队列的线程会先获得锁,按照?"FIFO(先进先出)" 的原则,对于每一个等待线程都是公平的。 非公平锁 非公平锁是抢占模式,线程不会关注队列中是否存在其他线程,也不会遵守先来后到的原则,直接尝试获取锁。 接下来进入正题,一起分析下 ReentrantLock 的底层是如何实现的。 ReentrantLock 的底层实现ReentrantLock 实现的前提是 AbstractQueuedSynchronizer(抽象队列同步器),简称 AQS,是 java.util.concurrent 的核心,常用的线程并发类 CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock 等都包括了一个继承自?AQS 抽象类的内部类。 同步标志位?state AQS 内部维护了一个同步标志位?state,用来实现同步加锁控制:
同步标志位 state 的初始值为 0,线程每加一次锁,state 就会加 1,也就是说,已经获得锁的线程再次加锁,state 值会再次加 1。可以看出,state 实际上表示的是已获得锁的线程进行加锁操作的次数。 CLH 队列 除了 state 同步标志位外,AQS 内部还使用一个 FIFO 的队列(也叫 CLH 队列)来表示排队等待锁的线程,当线程争抢锁失败后会封装成 Node 节点加入?CLH 队列中去。 Node 的代码实现:
分析代码可知,?每个 Node 节点都有两个指针,分别指向直接后继节点和直接前驱节点。 Node 节点的变化过程 当出现锁竞争以及释放锁的时候,AQS 同步队列中的 Node 节点会发生变化,如下图所示: ??
head 节点表示获取锁成功的节点,当头结点释放锁后,会唤醒后继节点,如果后继节点获得锁成功,就会把自己设置为头结点,节点的变化过程如下:
和设置 tail 的重新指向不同,设置 head 节点不需要用 CAS,是因为设置 head 节点是由获得锁的线程来完成的,而同步锁只能由一个线程获得,所以不需要 CAS 保证。只需要把 head 节点设置为原首节点的后继节点,并且断开原 head 节点的 next 引用即可。 除了前驱和后继节点,Node 类中还包括了?SHARED 和?EXCLUSIVE 节点,它们起到了什么作用呢?这就不得不介绍一下 AQS 的两种资源共享模式了。 AQS 的资源共享模式AQS 通过?EXCLUSIVE 和?SHARED 两个变量来定义独占模式或共享模式。 独占模式 独占模式是最常用的模式,使用范围很广,比如 ReentrantLock 的加锁和释放锁就是使用独占模式实现的。 独占模式中的核心加锁方法是 acquire():
这里首先调用 tryAcquire() 方法尝试获取锁,也就是尝试通过 CAS 修改 state 为 1,如果发现锁已经被当前线程占用,就执行重入,也就是给 state+1; 如果锁被其他线程占有,那么当前线程执行 tryAcquire 返回失败,则会执行 addWaiter() 方法在等待队列中添加一个独占式节点,addWaiter() 方法实现如下:
?写入队列后,需要挂起当前线程,代码如下:
再看下 shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 都做了哪些事:
通过以上代码可以看出,线程入队后能够挂起的前提是,它的前驱节点的状态为 SIGNAL,这意味着当前一个节点获取锁并且出队后,需要把后面的节点进行唤醒。 加锁说完了再说解锁,解锁的方法相比来说更加简单,核心方法是 release():
代码流程:先尝试释放锁,若释放成功,那么查看头结点的状态是否为 SIGNAL,如果是,则唤醒头结点的下个节点关联的线程,如果释放失败就返回 false 表示解锁失败。 其中的 tryRelease() 方法实现如下,详细流程见注释说明:
?共享模式 共享模式和独占模式最大的区别在于,共享模式具有传播的特性。 共享模式获取锁的方法为?acquireShared,相比于独占模式,共享模式的加锁多了一个步骤,即自己拿到资源后,还会去唤醒后继队友; 而共享模式释放锁的方法为?releaseShared,它会释放指定量的资源,如果成功释放且允许唤醒等待线程,会唤醒等待队列里的其他线程来获取资源。 本篇博客主要参考文章如下,非常感谢: |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 18:37:16- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |