一、ReentrantLock 概述

1.1 ReentrantLock 简介

故名思义,ReentrantLock 意为可重入锁,那么什么是可重入锁呢?可重入意为一个持有锁的线程可以对资源重复加锁而不会阻塞。比如下面这样:

    public synchronized void f1() {f2();}private synchronized void f2() { }

ReentrantLock 除了支持可重入以外,还支持定义公平与非公平策略,默认情况下采用非公平策略。公平是指等待时间最长的线程会优先获取锁,也就是获取锁是顺序的,可以理解为先到先得。

公平锁保证了锁的获取按照 FIFO(先进先出)原则,但是需要大量的线程切换。非公平锁虽然减少了线程之间的切换增大了其吞吐量,但是可能会造成线程“饥饿”。

1.2 使用方式

    public void f1() {ReentrantLock lock = new ReentrantLock();try {lock.lock();// ...} finally {lock.unlock();}}

synchronized 不同,使用 ReentrantLock 必须显示的加锁与释放锁。

1.3 与 synchronized 对比

相同点:

  • 可重入,同一线程可以多次获得同一个锁
  • 都保证了可见性和互斥性

不同点:

  • ReentrantLock 可响应中断、可轮回,为处理锁的不可用性提供了更高的灵活性,synchronized 不可以响应中断
  • ReentrantLock 是 API 级别的,synchronized 是 JVM 级别(JVM 内置属性)的,因此内置锁可以与特定的栈帧关联起来
  • ReentrantLock 可以实现公平锁,切可以实现带有时间限制的操作
  • ReentrantLock 通过 Condition 可以绑定多个条件

二、源码分析

了解了 ReentrantLock 的基本概念后,接下来就一起来看源码吧。其实 ReentrantLock 内的源码并不多,原因在于很多源码都在 AbstractQueuedSynchronizer 中,因此想要了解 ReentrantLock 源码的小伙伴需要先行了解下 AQS。

后面会给大家推荐几篇关于 AQS 源码介绍的文章,有兴趣的可以自行了解,或者到我的 GitHub 去查下相应的源码记录。点我前往 GitHub

2.1 构造函数

默认构造函数

    public ReentrantLock() {sync = new NonfairSync();}

有参构造函数

    public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

上面我们说了 ReentrantLock 支持两种同步策略,分别是公平与非公平,从这个构造函数中就能体现出来,相信大家看了这个构造函数也应该想到这两个 FairSyncNonfairSync 对象应该发挥着至关重要的作用。下面就来分别分析下对应的源码。

2.2 NonfairSync

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;// 加锁final void lock() {// 无锁重入,通过 CAS 第一次尝试修改同步状态值if (compareAndSetState(0, 1))// 设置当前线程独占setExclusiveOwnerThread(Thread.currentThread());else// 锁重入,同步状态值 + 1acquire(1);}// 尝试获取锁,直接调用 Sync 中的 nonfairTryAcquire 方法即可protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}

acquire 方法是 AQS 中的一个模板方法,这里我们就不多介绍了,还是要提醒下,大家应该先了解 AQS,再来看这篇文章。

tryAcquire 调用的是 nonfairTryAcquire 方法尝试去获取同步状态,接下来我们看 Sync 类中具体做了哪些实现。

2.3 Sync

    abstract static class Sync extends AbstractQueuedSynchronizer {abstract void lock();// 独占模式下尝试获取锁final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();// 获取同步状态int c = getState();// 0 表示无状态,独占锁模式下可理解为没有锁重入if (c == 0) {// 更新同步状态值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;}// 独占模式下尝试释放锁protected final boolean tryRelease(int releases) {// 减小同步状态int c = getState() - releases;// 不是当前线程抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果同步状态只为 0,表示可以释放资源,后面的线程可以尝试去获取锁if (c == 0) {free = true;setExclusiveOwnerThread(null);}// 更新减小后的同步状态值setState(c);return free;}}

上面并没有把 Sync 中的所有源码贴出来,这些源码已经足够我们说明问题了。

首先来看 nonfairTryAcquire 方法,该方法用于非公平模式下获取同步状态,我们知道 ReentrantLock 是支持可重入的锁,因此要考虑两种情况,没有锁重入与有锁重入。其实源码也很简单,有锁重入的情况下只要增加同步状态值即可,这里的同步状态值可以理解为锁重入的次数。等释放锁时将同步状态值再逐次减小,当减小为 0 时表示锁已经释放,这也是 tryRelease 方法中做的事情。

ReentrantLock 获取锁的方式是独占的,因此释放锁的过程,公平与非公平模式下是相同的。因为考虑到重入的情况,只有当同步状态减小到为 0 时,才返回 true 表示释放锁成功。

2.4 FairSync

    static final class FairSync extends Sync {final void lock() {acquire(1);}// 尝试获取锁protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取同步状态值int c = getState();// 在同步状态为 0 的情况下并不是所有线程都可以去获取同步状态,等待时间长的线程会优先获取同步状态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;}}

公平策略下获取同步状态的过程与非同步状态是类似的,只不过是多了一个 hasQueuedPredecessors 过程判断,这个方法用于判断当前线程对应的节点是否还有前驱节点,如果有则获取失败。下面是具体代码实现:

    public final boolean hasQueuedPredecessors() {Node t = tail; // Read fields in reverse initialization orderNode h = head;Node s;// s 表示头节点的后继节点,如果头节点的后继节点不是当前线程的节点,// 表示当前线程并非较先加入队列的线程,因此不能成功获取同步状态return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());}

非公平策略下释放同步状态与公平策略下相同,上面已经概述过了。

jdk1.8 源码阅读:https://github.com/zchen96/jdk1.8-source-code-read

参考资料

《JAVA 并发编程的艺术》
深入理解AbstractQueuedSynchronizer(一)
Java并发之AQS详解

Java8 ReentrantLock 源码分析相关推荐

  1. JUC AQS ReentrantLock源码分析

    Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比synchronized还 ...

  2. Java8 ThreadLocal 源码分析

    可参考文章: Java8 IdentityhashMap 源码分析 IdentityhashMap 与 ThreadLocalMap 一样都是采用线性探测法解决哈希冲突,有兴趣的可以先了解下 Iden ...

  3. Java并发编程-ReentrantLock源码分析

    一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...

  4. ReentrantLock源码分析

    ReentrantLock源码分析 前言 最近公司比较忙,整天忙着做项目.做需求,感觉整个人昏昏沉沉的,抬头看天空感觉都是灰色的~~,其实是杭州的天本来就是这个颜色,手动滑稽`~(^o^)/~`.废话 ...

  5. Java8 HashMap源码分析

    前言 今天,我们主要来研究一下在Java8中HashMap的数据结构及一些重要方法的具体实现.       研究HashMap的源代码之前,我们首先来研究一下常用的三种数据结构:数组.链表和红黑树. ...

  6. Java8 PriorityBlockingQueue源码分析

    在看这篇总结之前,建议大家先熟悉一下 PriorityQueue,这里主要介绍 PriorityBlockingQueue 一些特殊的性质,关于优先级队列的知识不作着重介绍,因为过程与 Priorit ...

  7. Java8 CopyOnWriteArrayList 源码分析

    一.CopyOnWriteArrayList 概述 1.1 概念概述 CopyOnWriteArrayList 是 juc 包下一个线程安全的并发容器,底层使用数组实现.CopyOnWrite 顾名思 ...

  8. Java并发编程 ReentrantLock 源码分析

    ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...

  9. 【ReentrantLock源码分析】1.xdb中的使用 2.获取和阻塞(阻塞前的一些死心不改)的源码

    1.xdb中的使用例子 在xdb中,我们大概执行业务时的流程简化如下: package org.example.testReentrantLock;import java.util.concurren ...

最新文章

  1. 开源库jemalloc简介
  2. C# 读取Excel中的时间
  3. 布道微服务_10注册中心与RPC框架的选型
  4. goroutine sync.RWMutex读写锁RLock的使用
  5. Android ProgressBar 反向进度条/进度条从右到左走
  6. Java代码判断数据库中某张表是否存在
  7. Docker for windows 10
  8. 哈工大计算机专研和学研的区别,哈工大教授发表SCI和核心期刊共26篇,发明专利6项,在交叉学科领域大放异彩!...
  9. java上传视频代码下载_java 实现视频上传
  10. Java使用swagger时显示实体类注解问题
  11. 频率分布直方图组距如何确定_频率分布有关的概念
  12. Rinetd.exe 通过 instsrv.exe/srvany.exe 注册服务实现稳定端口转发
  13. Redis反序列化错误Could not read JSON: Cannot construct instance of `java.util.ArrayList$SubList`
  14. 引入 DTM 以支持 ABP 的多租户多数据库场景
  15. 用层次分析法解决购买笔记本电脑的问题
  16. 解压jar包修改配置文件,解压、修改、压缩、运行
  17. Linux命令教程第三期
  18. 计算机通信机房消防要求,信息机房对环境有什么要求
  19. html5开卷考试,美国AP开卷考试
  20. 如何运营一个女性社区?

热门文章

  1. GitHub轻松阅读微服务实战项目流程详解【第一天:数据库表设计及其环境搭建、项目运行】
  2. seata+nacos出现failed to req API:/nacos/v1/ns/instance/beat after all servers([127.0.0.1:8848])
  3. Visual C++——《可视化编程技术》课程考核
  4. BugKuCTF 加密 聪明的小羊
  5. Rightmost Digit
  6. Android Activity的生命周期、意图(Intent)
  7. 注册域名的时候一定要注意的事项
  8. APP远程调试及网络自动化测试
  9. ARSessionConfiguration报错问题
  10. Implicit conversion from enumeration type 'enum CGImageAlphaInfo' to different enumeration type 'CGB