ReentrantLock和Synchronized都是Java开发中最常用的锁,与Synchronized这种JVM内置锁不同的是,ReentrantLock提供了更丰富的语义。可以创建公平锁或非公平锁、响应中断、超时等待、按条件唤醒等。在某些场景下,使用ReentrantLock更适合,功能更强大。

前两篇文章,我们分析了AQS的加锁流程、以及源码实现。当时我们就说了,AQS使用了模板设计模式,父类中定义加锁流程,子类去实现具体的加锁逻辑。所以大部分加锁代码已经在父类AQS中实现了,导致ReentrantLock的源码非常简单,一块学习一下。

先看一下ReentrantLock怎么使用?

1. ReentrantLock的使用

  1. /**
  2. * @author 一灯架构
  3. * @apiNote ReentrantLock示例
  4. **/
  5. public class ReentrantLockDemo {
  6. public static void main(String[] args) {
  7. // 1. 创建ReentrantLock对象
  8. ReentrantLock lock = new ReentrantLock();
  9. // 2. 加锁
  10. lock.lock();
  11. try {
  12. // 3. 这里执行具体的业务逻辑
  13. } finally {
  14. // 4. 释放锁
  15. lock.unlock();
  16. }
  17. }
  18. }

可以看到ReentrantLock的使用非常简单,调用lock加锁,unlock释放锁,需要配置try/finally使用,保证在代码执行出错的时候也能释放锁。

ReentrantLock也可以配合Condition条件使用,具体可以翻一下前几篇文章中BlockingQueue的源码解析,那里面有ReentrantLock的实际使用。

再看一下ReentrantLock的类结构

2. ReentrantLock类结构

  1. // 实现Lock接口
  2. public class ReentrantLock implements Lock {
  3. // 只有一个Sync同步变量
  4. private final Sync sync;
  5. // Sync继承自AQS,主要逻辑都在这里面
  6. abstract static class Sync extends AbstractQueuedSynchronizer {
  7. }
  8. // Sync的两个子类,分别实现了公平锁和非公平锁
  9. static final class FairSync extends Sync {
  10. }
  11. static final class NonfairSync extends Sync {
  12. }
  13. }

可以看出ReentrantLock的类结构非常简单,实现了Lock接口。

类里面有两个静态内部类,分别实现公平锁和非公平锁。

看一下Lock接口中,定义了哪些方法?

  1. public interface Lock {
  2. // 加锁
  3. void lock();
  4. // 加可中断的锁
  5. void lockInterruptibly() throws InterruptedException;
  6. // 尝试加锁
  7. boolean tryLock();
  8. // 一段时间内,尝试加锁
  9. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  10. // 释放锁
  11. void unlock();
  12. // 新建条件状态
  13. Condition newCondition();
  14. }

就是一些使用锁的常用方法。

在上篇文章中浏览AQS源码的时候,了解到AQS定义了一些有关具体加锁、释放锁的抽象方法,留给子类去实现,再看一下有哪些抽象方法:

  1. // 加独占锁
  2. protected boolean tryAcquire(int arg) {
  3. throw new UnsupportedOperationException();
  4. }
  5. // 释放独占锁
  6. protected boolean tryRelease(int arg) {
  7. throw new UnsupportedOperationException();
  8. }
  9. // 加共享锁
  10. protected int tryAcquireShared(int arg) {
  11. throw new UnsupportedOperationException();
  12. }
  13. // 释放共享锁
  14. protected boolean tryReleaseShared(int arg) {
  15. throw new UnsupportedOperationException();
  16. }
  17. // 判断是否是当前线程正在持有锁
  18. protected boolean isHeldExclusively() {
  19. throw new UnsupportedOperationException();
  20. }

由于ReentrantLock使用的是独占锁,所以只需要实现独占锁相关的方法就可以了。

3. ReentrantLock源码解析

3.1 ReentrantLock构造方法

  1. // 默认的构造方法,使用非公平锁
  2. public ReentrantLock() {
  3. sync = new NonfairSync();
  4. }
  5. // 传true,可以指定使用公平锁
  6. public ReentrantLock(boolean fair) {
  7. sync = fair ? new FairSync() : new NonfairSync();
  8. }

在创建ReentrantLock对象的时候,可以指定使用公平锁还是非公平锁,默认使用非公平锁,显然非公平锁的性能更好。

先思考一个面试常考问题,公平锁和非公平锁是怎么实现的?

3.2 非公平锁源码

先看一下加锁源码:

从父类ReentrantLock的加锁方法入口:

  1. public class ReentrantLock implements Lock {
  2. // 加锁入口方法
  3. public void lock() {
  4. // 调用Sync中加锁方法
  5. sync.lock();
  6. }
  7. }

在子类NonfairSync的加锁方法:

  1. // 非公平锁
  2. static final class NonfairSync extends Sync {
  3. // 加锁
  4. final void lock() {
  5. // 1. 先尝试加锁(使用CAS设置state=1)
  6. if (compareAndSetState(0, 1))
  7. // 2. 加锁成功,就把当前线程设置为持有锁线程
  8. setExclusiveOwnerThread(Thread.currentThread());
  9. else
  10. // 3. 没加锁成功,再调用父类AQS中实际的加锁逻辑
  11. acquire(1);
  12. }
  13. }

加锁逻辑也很简单,先尝试使用CAS加锁(也就是把state从0设置成1),加锁成功,就把当前线程设置为持有锁线程。

设计者很聪明,在锁竞争不激烈的情况下,很大概率可以加锁成功,也就不用走else中复杂的加锁逻辑了。

如果没有加锁成功,还是需要走else中调用父类AQS的acquire方法,而acquire又需要调用子类的tryAcquire方法。

调用链路就是下面这样:

根据调用链路,实际的加锁逻辑在Sync.nonfairTryAcquire方法里面。

  1. abstract static class Sync extends AbstractQueuedSynchronizer {
  2. // 非公平锁的最终加锁方法
  3. final boolean nonfairTryAcquire(int acquires) {
  4. final Thread current = Thread.currentThread();
  5. // 1. 获取同步状态
  6. int c = getState();
  7. // 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
  8. if (c == 0) {
  9. if (compareAndSetState(0, acquires)) {
  10. // 3. 加锁成功,就把当前线程设置为持有锁线程
  11. setExclusiveOwnerThread(current);
  12. return true;
  13. }
  14. // 4. 如果当前线程已经持有锁,执行可重入的逻辑
  15. } else if (current == getExclusiveOwnerThread()) {
  16. // 5. 加锁次数+acquires
  17. int nextc = c + acquires;
  18. // 6. 超过tnt类型最大值,溢出了
  19. if (nextc < 0)
  20. throw new Error("Maximum lock count exceeded");
  21. setState(nextc);
  22. return true;
  23. }
  24. return false;
  25. }
  26. }

再看一下释放锁的调用流程,公平锁和非公平锁流程是一样的,最终都是执行Sync.tryRelease方法:

  1. abstract static class Sync extends AbstractQueuedSynchronizer {
  2. // 释放锁
  3. protected final boolean tryRelease(int releases) {
  4. // 1. 同步状态减去释放锁次数
  5. int c = getState() - releases;
  6. // 2. 校验当前线程不持有锁,就报错
  7. if (Thread.currentThread() != getExclusiveOwnerThread())
  8. throw new IllegalMonitorStateException();
  9. boolean free = false;
  10. // 3. 判断同步状态是否等于0,无锁后,就删除持有锁的线程
  11. if (c == 0) {
  12. free = true;
  13. setExclusiveOwnerThread(null);
  14. }
  15. setState(c);
  16. return free;
  17. }
  18. }

再看一下公平锁的源码

3.3 公平锁源码

先看一下公平锁的加锁流程:

最终的加锁方法是FairSync.tryAcquire,看一下具体逻辑:

  1. static final class FairSync extends Sync {
  2. // 实现父类的加锁逻辑
  3. protected final boolean tryAcquire(int acquires) {
  4. final Thread current = Thread.currentThread();
  5. // 1. 获取同步状态
  6. int c = getState();
  7. // 2. state=0表示无锁,先尝试加锁(使用CAS设置state=1)
  8. if (c == 0) {
  9. // 3. 判断当前线程是不是头节点的下一个节点(讲究先来后到)
  10. if (!hasQueuedPredecessors() &&
  11. compareAndSetState(0, acquires)) {
  12. setExclusiveOwnerThread(current);
  13. return true;
  14. }
  15. // 4. 如果当前线程已经持有锁,执行可重入的逻辑
  16. } else if (current == getExclusiveOwnerThread()) {
  17. // 5. 加锁次数+acquires
  18. int nextc = c + acquires;
  19. // 6. 超过tnt类型最大值,溢出了
  20. if (nextc < 0)
  21. throw new Error("Maximum lock count exceeded");
  22. setState(nextc);
  23. return true;
  24. }
  25. return false;
  26. }
  27. // 判断当前线程是不是头节点的下一个节点(讲究先来后到)
  28. public final boolean hasQueuedPredecessors() {
  29. Node t = tail;
  30. Node h = head;
  31. Node s;
  32. return h != t &&
  33. ((s = h.next) == null || s.thread != Thread.currentThread());
  34. }
  35. }

公平锁的释放锁逻辑跟非公平锁一样,上面已经讲过。

4. 总结

看完了ReentrantLock的所有源码,是不是觉得ReentrantLock很简单。

由于加锁流程的编排工作已经在父类AQS中实现,子类只需要实现具体的加锁逻辑即可。

加锁逻辑也很简单,也就是修改同步状态state的值和持有锁的线程exclusiveOwnerThread。

我是「一灯架构」,如果本文对你有帮助,欢迎各位小伙伴点赞、评论和关注,感谢各位老铁,我们下期见

干货,深入剖析ReentrantLock源码,推荐收藏的更多相关文章

  1. Java并发系列[5]----ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可 ...

  2. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  3. Java并发编程笔记之ReentrantLock源码分析

    ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞后放入该锁的AQS阻塞队列里面. 首先我们先看一下ReentrantLock的类图结构,如下图所示 ...

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

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

  5. 第六章 ReentrantLock源码解析2--释放锁unlock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  6. java多线程---ReentrantLock源码分析

    ReentrantLock源码分析 基础知识复习 synchronized和lock的区别 synchronized是非公平锁,无法保证线程按照申请锁的顺序获得锁,而Lock锁提供了可选参数,可以配置 ...

  7. ReentrantLock源码分析--jdk1.8

    JDK1.8 ArrayList源码分析--jdk1.8LinkedList源码分析--jdk1.8HashMap源码分析--jdk1.8AQS源码分析--jdk1.8ReentrantLock源码分 ...

  8. JUC AQS ReentrantLock源码分析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6 ...

  9. java源码-ReentrantLock源码分析-1

    ReentrantLock 继承于lock是比较常用的独占锁,接下来我们来分析一下ReentrantLock源码以及接口设计: Sync是ReentrantLock的内部静态抽象类继承Abstract ...

随机推荐

  1. ViewGroup事件分发源码分析

    1.AndroidStudio源码调试方式 AndroidStudio默认是支持一部分源码调试的,但是build.gradle(app) 中的sdk版本要保持一致, 最好是编译版本.运行版本以及手机的 ...

  2. 第五十一篇:webpack中的loader(二) --less-loader

    好家伙 先扩充一下知识点: 什么是.less文件? 作为一名前端开发的同学,很多时候我们都无法避免地要去写大量的 CSS 代码, 而且耗费的时间还不少,所以学习一种能够提升开发效率的 CSS 预处理器 ...

  3. 异步编程promise

    异步编程发展 异步编程经历了 callback.promise.async/await.generator四个阶段,其中promise和async/await使用最为频繁,而generator因为语法 ...

  4. 《Java基础——抽象与接口》

    Java基础--抽象与接口       一.抽象: 规则: 关键字 abstract 修饰的类称为抽象类. 子类通过关键字extends实现继承. 关键字 abstract 修饰的方法称为抽象方法,抽 ...

  5. 《Win10——常用快捷键》

    Win10--常用快捷键       Ctrl+C:复制 Ctrl+V:粘贴 Ctrl+A:全选 Ctrl+X:剪切 Ctrl+D:删除 Ctrl+Z:撤销 Ctrl+Y:反撤销 Ctrl+Shift ...

  6. MySQL DDL执行方式-Online DDL介绍

    1 引言 大家好,今天与大家一起分享一下 mysql DDL执行方式. 一般来说MySQL分为DDL(定义)和DML(操作). DDL:Data Definition Language,即数据定义语言 ...

  7. 100个Linux Shell脚本经典案例(附PDF)

    转载自:https://mp.weixin.qq.com/s/tCKAM67_7K7q2vJthaIsDQ 原文链接:https://wenku.baidu.com/view/4f089430a116 ...

  8. Linux服务器上MinIO生产部署的内核调优

    #!/bin/bash cat > sysctl.conf <<EOF # maximum number of open files/file descriptors fs.file ...

  9. 第1篇----Istio原理篇

    Istio是什么 ◎ Istio是一个用于服务治理的开放平台. ◎ Istio是一个Service Mesh形态的用于服务治理的开放平台. ◎ Istio是一个与Kubernetes紧密结合的适用于云 ...

  10. vue this.$router.push query传递对象方法

    this.$router.push({ path: '/home', query: { params: JSON.stringify({ name: 'lokka', age: 18 }) } }); ...