使用 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 实现原理相关推荐

  1. java lock的原理,Java中Lock原理探究

    在对于lock锁的使用上,很多人只是掌握了最基础的方法,但是对实现的过程不是很清楚.这里我们对lock锁功能的实现进行分析,以ReentrantLock为例,分析它的锁类型,并对相关的调用方法进行展示 ...

  2. ReentrantLock实现原理深入探究

    前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...

  3. 《转》ReentrantLock实现原理深入探究

    五月的仓颉 博客园 管理 随笔 - 202  文章 - 0  评论 - 1646 ReentrantLock实现原理深入探究 前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写Ree ...

  4. java锁的概念,Java ReentrantLock锁机制概念篇

    分享Java锁机制实现原理,细节涉及volatile修饰符.CAS原子操作.park阻塞线程与unpark唤醒.双向链表.锁的公平性与非公平性.独占锁和共享锁.线程等待await.线程中断interr ...

  5. 只需这篇文章java线程池原理便懂了!♥♥

    模拟Java线程池运行原理 在了解线程池之前,我们先来谈谈线程的状态转换 线程常用方法及种类 线程池实现原理和线程池概念 四种线程池 线程池的组成 代码 自定义线程池功能简单 在了解线程池之前,我们先 ...

  6. ReentrantLock 实现原理笔记(一)

    java.util.concurrent.locks.ReentrantLock exclusive : adj. (个人或集体) 专用的,专有的,独有的,独占的; 排外的; 不愿接收新成员(尤指较低 ...

  7. ReentrantLock实现原理(可重入锁 )

    多线程-同步锁相关文章 ----synchronized原理(锁关键字) ----ReentrantLock实现原理(可重入锁 ) ----Volatile关键字原理 ----CAS原理详解 1. A ...

  8. Java 虚拟机(JVM)原理介绍

    Java 虚拟机[JVM]原理介绍 1.概述 2.Java类的加载原理机制 2.1 .Java类的加载过程 2.2 .Class loader (类加载器) 2.2.1 类的生命周期 2.2.1.1 ...

  9. java基础--java中HashMap原理

    java中HashMap原理 内推军P21 P22 1.为什么用HashMap? HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表 ...

最新文章

  1. 推荐10款优秀的JavaScript Web UI库 框架和套件
  2. sip-selvet 环境搭建
  3. 全国计算机等级考试题库二级C操作题100套(第60套)
  4. 单纯形法求最小值的检验数_【运筹学】单纯形法(笔记和思考)
  5. 我的WCF之旅(4):WCF中的序列化[下篇]
  6. C#.Net工作笔记015---C#中Decimal类型四舍五入_小数点截位
  7. 为了完成月入三万的目标,我都做了哪些准备?
  8. python 按日期筛选数据并计算均值
  9. 理解苏宁:互联网转型之战
  10. iNodeClient 校园网客户端在linux环境下的使用方法
  11. 学习open62541 --- [15] 使用建模工具UaModeler
  12. 51822 proximity
  13. ntoskrnl导致的蓝屏死机问题
  14. Communication-Efficient Federated Learning for Wireless Edge Intelligence in IoT
  15. 使用 Java 解决现代应用程序开发挑战
  16. 孟凯:卖菜的难道一定要终身卖菜吗?
  17. 天眼全流量系统的详细说明
  18. APP,实现多国语言动态切换
  19. SunFMEA培训-PFMEA中常见的预防措施
  20. HTML5期末考核大作业:美食主题网站设计——沪上美食(9页)带Flash动画视频导航下拉表单 HTML+CSS+JavaScript (1)

热门文章

  1. Java Date 日期 时间 相关方法
  2. 路由器qos设置包括哪些内容
  3. Python初学者之ImportError: No module named moviepy.editor 的解决办法
  4. 有限元基础: Jacobian 矩阵和高斯积分
  5. simulink中s-function使用
  6. matlab怎么求矩阵的范数
  7. select,epoll,poll比较(网络资源总结)
  8. Go语言简单的TCP编程
  9. Qt网络编程——TCP服务器与客户端互发信息
  10. mysql外键怎么写sql文_mysql 创建外键sql语句