java ReentrantLock 实现原理
使用 synchronized
来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。
而 ReentrantLock
就是一个普通的类,它是基于 AQS(AbstractQueuedSynchronizer)
来实现的。
是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。
AQS
是Java
并发包里实现锁、同步的一个重要的基础框架。
锁类型
ReentrantLock 分为公平锁和非公平锁,可以通过构造方法来指定具体类型:
//默认非公平锁public ReentrantLock() {sync = new NonfairSync();}//公平锁public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
默认一般使用非公平锁,它的效率和吞吐量都比公平锁高的多(后面会分析具体原因)。
获取锁
通常的使用方式如下:
private ReentrantLock lock = new ReentrantLock();public void run() {lock.lock();try {//do bussiness} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
公平锁获取锁
首先看下获取锁的过程:
public void lock() {sync.lock();}
可以看到是使用 sync
的方法,而这个方法是一个抽象方法,具体是由其子类(FairSync
)来实现的,以下是公平锁的实现:
final void lock() {acquire(1);}//AbstractQueuedSynchronizer 中的 acquire()public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
第一步是尝试获取锁(tryAcquire(arg)
),这个也是由其子类实现:
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}}
首先会判断 AQS
中的 state
是否等于 0,0 表示目前没有其他线程获得锁,当前线程就可以尝试获取锁。
注意:尝试之前会利用 hasQueuedPredecessors()
方法来判断 AQS 的队列中中是否有其他线程,如果有则不会尝试获取锁(这是公平锁特有的情况)。
如果队列中没有线程就利用 CAS 来将 AQS 中的 state 修改为1,也就是获取锁,获取成功则将当前线程置为获得锁的独占线程(setExclusiveOwnerThread(current)
)。
如果 state
大于 0 时,说明锁已经被获取了,则需要判断获取锁的线程是否为当前线程(ReentrantLock
支持重入),是则需要将 state + 1
,并将值更新。
写入队列
如果 tryAcquire(arg)
获取锁失败,则需要用 addWaiter(Node.EXCLUSIVE)
将当前线程写入队列中。
写入之前需要将当前线程包装为一个 Node
对象(addWaiter(Node.EXCLUSIVE)
)。
AQS 中的队列是由 Node 节点组成的双向链表实现的。
包装代码:
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}
首先判断队列是否为空,不为空时则将封装好的 Node
利用 CAS
写入队尾,如果出现并发写入失败就需要调用 enq(node);
来写入了。
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}
这个处理逻辑就相当于自旋
加上 CAS
保证一定能写入队列。
挂起等待线程
写入队列之后需要将当前线程挂起(利用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
):
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}
首先会根据 node.predecessor()
获取到上一个节点是否为头节点,如果是则尝试获取一次锁,获取成功就万事大吉了。
如果不是头节点,或者获取锁失败,则会根据上一个节点的 waitStatus
状态来处理(shouldParkAfterFailedAcquire(p, node)
)。
waitStatus
用于记录当前节点的状态,如节点取消、节点等待等。
shouldParkAfterFailedAcquire(p, node)
返回当前线程是否需要挂起,如果需要则调用 parkAndCheckInterrupt()
:
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}
他是利用 LockSupport
的 part
方法来挂起当前线程的,直到被唤醒。
非公平锁获取锁
公平锁与非公平锁的差异主要在获取锁:
公平锁就相当于买票,后来的人需要排到队尾依次买票,不能插队。
而非公平锁则没有这些规则,是抢占模式,每来一个人不会去管队列如何,直接尝试获取锁。
非公平锁:
final void lock() {//直接尝试获取锁if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}
公平锁:
final void lock() {acquire(1);}
还要一个重要的区别是在尝试获取锁时tryAcquire(arg)
,非公平锁是不需要判断队列中是否还有其他线程,也是直接尝试获取锁:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//没有 !hasQueuedPredecessors() 判断if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
释放锁
公平锁和非公平锁的释放流程都是一样的:
public void unlock() {sync.release(1);}public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)//唤醒被挂起的线程unparkSuccessor(h);return true;}return false;}//尝试释放锁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;}
首先会判断当前线程是否为获得锁的线程,由于是重入锁所以需要将 state
减到 0 才认为完全释放锁。
释放之后需要调用 unparkSuccessor(h)
来唤醒被挂起的线程。
总结
由于公平锁需要关心队列的情况,得按照队列里的先后顺序来获取锁(会造成大量的线程上下文切换),而非公平锁则没有这个限制。
所以也就能解释非公平锁的效率会被公平锁更高。
java ReentrantLock 实现原理相关推荐
- java lock的原理,Java中Lock原理探究
在对于lock锁的使用上,很多人只是掌握了最基础的方法,但是对实现的过程不是很清楚.这里我们对lock锁功能的实现进行分析,以ReentrantLock为例,分析它的锁类型,并对相关的调用方法进行展示 ...
- ReentrantLock实现原理深入探究
前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...
- 《转》ReentrantLock实现原理深入探究
五月的仓颉 博客园 管理 随笔 - 202 文章 - 0 评论 - 1646 ReentrantLock实现原理深入探究 前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写Ree ...
- java锁的概念,Java ReentrantLock锁机制概念篇
分享Java锁机制实现原理,细节涉及volatile修饰符.CAS原子操作.park阻塞线程与unpark唤醒.双向链表.锁的公平性与非公平性.独占锁和共享锁.线程等待await.线程中断interr ...
- 只需这篇文章java线程池原理便懂了!♥♥
模拟Java线程池运行原理 在了解线程池之前,我们先来谈谈线程的状态转换 线程常用方法及种类 线程池实现原理和线程池概念 四种线程池 线程池的组成 代码 自定义线程池功能简单 在了解线程池之前,我们先 ...
- ReentrantLock 实现原理笔记(一)
java.util.concurrent.locks.ReentrantLock exclusive : adj. (个人或集体) 专用的,专有的,独有的,独占的; 排外的; 不愿接收新成员(尤指较低 ...
- ReentrantLock实现原理(可重入锁 )
多线程-同步锁相关文章 ----synchronized原理(锁关键字) ----ReentrantLock实现原理(可重入锁 ) ----Volatile关键字原理 ----CAS原理详解 1. A ...
- Java 虚拟机(JVM)原理介绍
Java 虚拟机[JVM]原理介绍 1.概述 2.Java类的加载原理机制 2.1 .Java类的加载过程 2.2 .Class loader (类加载器) 2.2.1 类的生命周期 2.2.1.1 ...
- java基础--java中HashMap原理
java中HashMap原理 内推军P21 P22 1.为什么用HashMap? HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表 ...
最新文章
- 推荐10款优秀的JavaScript Web UI库 框架和套件
- sip-selvet 环境搭建
- 全国计算机等级考试题库二级C操作题100套(第60套)
- 单纯形法求最小值的检验数_【运筹学】单纯形法(笔记和思考)
- 我的WCF之旅(4):WCF中的序列化[下篇]
- C#.Net工作笔记015---C#中Decimal类型四舍五入_小数点截位
- 为了完成月入三万的目标,我都做了哪些准备?
- python 按日期筛选数据并计算均值
- 理解苏宁:互联网转型之战
- iNodeClient 校园网客户端在linux环境下的使用方法
- 学习open62541 --- [15] 使用建模工具UaModeler
- 51822 proximity
- ntoskrnl导致的蓝屏死机问题
- Communication-Efficient Federated Learning for Wireless Edge Intelligence in IoT
- 使用 Java 解决现代应用程序开发挑战
- 孟凯:卖菜的难道一定要终身卖菜吗?
- 天眼全流量系统的详细说明
- APP,实现多国语言动态切换
- SunFMEA培训-PFMEA中常见的预防措施
- HTML5期末考核大作业:美食主题网站设计——沪上美食(9页)带Flash动画视频导航下拉表单 HTML+CSS+JavaScript (1)