一、前言

在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到过ReentrantLock,ReentrantLock表示下面具体分析ReentrantLock源码。

二、ReentrantLock数据结构

ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchronizer的数据结构,关于AQS的数据结构,在前一篇已经介绍过,不再累赘。

三、ReentrantLock源码分析

3.1 类的继承关系

public class ReentrantLock implements Lock, java.io.Serializable

说明:ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

3.2 类的内部类

ReentrantLock总共有三个内部类,并且三个内部类时紧密相关的,下面先看三个类的关系。

说明:ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。

1. Sync类

Sync类的源码如下

abstract static class Sync extends AbstractQueuedSynchronizer {// 序列号private static final long serialVersionUID = -5179523762034025860L;// 获取锁abstract void lock();// 非公平方式获取final boolean nonfairTryAcquire(int acquires) {// 当前线程final Thread current = Thread.currentThread();// 获取状态int c = getState();if (c == 0) { // 表示没有线程正在竞争该锁if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用// 设置当前线程独占
                    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; if (c == 0) {free = true;// 已经释放,清空独占setExclusiveOwnerThread(null); }// 设置标识
            setState(c); return free; }// 判断资源是否被当前线程占有protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();}// 新生一个条件final ConditionObject newCondition() {return new ConditionObject();}// Methods relayed from outer class// 返回资源的占用线程final Thread getOwner() {        return getState() == 0 ? null : getExclusiveOwnerThread();}// 返回状态final int getHoldCount() {            return isHeldExclusively() ? getState() : 0;}// 资源是否被占用final boolean isLocked() {        return getState() != 0;}/*** Reconstitutes the instance from a stream (that is, deserializes it).*/// 自定义反序列化逻辑private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();setState(0); // reset to unlocked state
        }}

说明:Sync类存在如下方法和作用如下。

2. NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下。

// 非公平锁static final class NonfairSync extends Sync {// 版本号private static final long serialVersionUID = 7316153563782823691L;// 获得锁final void lock() {if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用// 把当前线程设置独占了锁
                setExclusiveOwnerThread(Thread.currentThread());else // 锁已经被占用,或者set失败// 以独占模式获取对象,忽略中断acquire(1); }protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}

说明:从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

3. FairSyn类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下。

// 公平锁static final class FairSync extends Sync {// 版本序列化private static final long serialVersionUID = -3000897897090466540L;final void lock() {// 以独占模式获取对象,忽略中断acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/// 尝试公平获取锁protected final boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();// 获取状态int c = getState();if (c == 0) { // 状态为0if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功// 设置当前线程独占
                    setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据// 下一个状态int nextc = c + acquires;if (nextc < 0) // 超过了int的表示范围throw new Error("Maximum lock count exceeded");// 设置状态
                setState(nextc);return true;}return false;}}

说明:跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。

说明:可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

3.3 类的属性

public class ReentrantLock implements Lock, java.io.Serializable {// 序列号private static final long serialVersionUID = 7373984872572414699L;    // 同步队列private final Sync sync;
}

说明:ReentrantLock类的sync非常重要,对ReentrantLock类的操作大部分都直接转化为对Sync和AbstractQueuedSynchronizer类的操作。

3.4 类的构造函数

1. ReentrantLock()型构造函数

public ReentrantLock() {// 默认非公平策略sync = new NonfairSync();}

说明:可以看到默认是采用的非公平策略获取锁。

2. ReentrantLock(boolean)型构造函数

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

说明:可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略。

3.5 类的核心函数分析

通过分析ReentrantLock的源码,可知对其操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。

所以可知,在ReentrantLock的背后,是AQS对其服务提供了支持,由于之前我们分析AQS的核心源码,遂不再累赘。下面还是通过例子来更进一步分析源码。

四、示例分析

4.1 公平锁

package com.hust.grid.leesf.abstractqueuedsynchronizer;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class MyThread extends Thread {private Lock lock;public MyThread(String name, Lock lock) {super(name);this.lock = lock;}public void run () {lock.lock();try {System.out.println(Thread.currentThread() + " running");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}
}public class AbstractQueuedSynchonizerDemo {public static void main(String[] args) throws InterruptedException {Lock lock = new ReentrantLock(true);MyThread t1 = new MyThread("t1", lock);        MyThread t2 = new MyThread("t2", lock);MyThread t3 = new MyThread("t3", lock);t1.start();t2.start();    t3.start();}
}

运行结果(某一次):
Thread[t1,5,main] running
Thread[t2,5,main] running Thread[t3,5,main] running 

说明:该示例使用的是公平策略,由结果可知,可能会存在如下一种时序。

说明:首先,t1线程的lock操作 -> t2线程的lock操作 -> t3线程的lock操作 -> t1线程的unlock操作 -> t2线程的unlock操作 -> t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。

① t1线程执行lock.lock,下图给出了方法调用中的主要方法。

说明:由调用流程可知,t1线程成功获取了资源,可以继续执行。

② t2线程执行lock.lock,下图给出了方法调用中的主要方法。

说明:由上图可知,最后的结果是t2线程会被禁止,因为调用了LockSupport.park。

③ t3线程执行lock.lock,下图给出了方法调用中的主要方法。

说明:由上图可知,最后的结果是t3线程会被禁止,因为调用了LockSupport.park。

④ t1线程调用了lock.unlock,下图给出了方法调用中的主要方法。

说明:如上图所示,最后,head的状态会变为0,t2线程会被unpark,即t2线程可以继续运行。此时t3线程还是被禁止。

⑤ t2获得cpu资源,继续运行,由于t2之前被park了,现在需要恢复之前的状态,下图给出了方法调用中的主要方法。

说明:在setHead函数中会将head设置为之前head的下一个结点,并且将pre域与thread域都设置为null,在acquireQueued返回之前,sync queue就只有两个结点了。

⑥ t2执行lock.unlock,下图给出了方法调用中的主要方法。

说明:由上图可知,最终unpark t3线程,让t3线程可以继续运行。

⑦ t3线程获取cpu资源,恢复之前的状态,继续运行。

说明:最终达到的状态是sync queue中只剩下了一个结点,并且该节点除了状态为0外,其余均为null。

⑧ t3执行lock.unlock,下图给出了方法调用中的主要方法。

说明:最后的状态和之前的状态是一样的,队列中的一个结点会被GC,最后队列会为空。

使用公平策略和Condition的情况可以参考上一篇关于AQS的源码示例分析部分,不再累赘。

五、总结

再掌握了AQS后,再来分析ReentrantLock的源码,就会非常简单,因为ReentrantLock的绝大部分操作都是基于AQS类的。所以,进行分析时要找准方向,就会事半功倍。谢谢各位园友观看~

Java并发编程-ReentrantLock源码分析相关推荐

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

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

  2. 【java】java 并发编程 Condition 源码分析

    文章目录 1.概述 1.2 案例 3.2 实现方法顺序调用 2.源码解析 2.1 接口方法 2.2 继承 2.3 队列 2.4 await 分析 2.4.1 线程1 await 2.4.2 线程2 a ...

  3. 【java】java 并发编程 CyclicBarrier 源码分析

    文章目录 1.概述 4.源码阅读 4.1 构造方法 4.2 Generation 4.3 属性Condition 4.4 await方法 4.4.1 BrokenBarrierException 4. ...

  4. Java并发编程 LockSupport源码分析

    这个类比较简单,是一个静态类,不需要实例化直接使用,底层是通过java未开源的Unsafe直接调用底层操作系统来完成对线程的阻塞. 1 package java.util.concurrent.loc ...

  5. java futuretask 源码_java并发编程——FutureTask源码分析

    FutureTask的简单示例: FutureTask的应用场景,如果在当前线程中需要执行比较耗时的操作,但又不想阻塞当前线程时,可以把这些作业交给FutureTask,另开一个线程在后台完成,当当前 ...

  6. Java并发编程-AQS源码之条件队列

    System.out.println(name + "==>成功获取到锁" + lock); try { condition.await(); } catch (Interr ...

  7. java futuretask 源码解析_Java异步编程——深入源码分析FutureTask

    Java的异步编程是一项非常常用的多线程技术. 之前通过源码详细分析了ThreadPoolExecutor<你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识&g ...

  8. JUC AQS ReentrantLock源码分析

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

  9. ReentrantLock源码分析

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

最新文章

  1. php之static静态变量详解
  2. mysql 开启慢查明_mysql开启慢查询方法
  3. Linux的辅助数据和传递文件描述符
  4. 有助提高效率的Web前端开发框架有哪些?
  5. php excel parser pro v4.2,PHP Excel Parser Pro v4.2怎么用
  6. 计算机组成原理课程设计 从1加到10,带进位左移三位,计算机组成原理课程设计--乘除法运算.docx...
  7. 怎样批量替换Word中的文字?这一招Word文字替换技巧你会吗
  8. EPC 的RFID 标准体系
  9. kubernetes 的 滚动升级和回滚(rollout)
  10. React Native 实践之携程 Moles 框架
  11. 第八章 性能优化 之 内存优化(一)
  12. 实践数据湖iceberg 第二十一课 flink1.13.5 + iceberg0.131 CDC(测试成功INSERT,变更操作失败)
  13. 数论作业 —— 公约数公倍数问题
  14. Using System.Messaging引用报错解决办法
  15. ie兼容模式下跨域访问问题的解决
  16. 他博士毕业论文写了1255页,28岁评教授,38岁当院士!
  17. CryEngine的maya插件安装
  18. 集合框架背后的数据结构
  19. GPFS和Lustre之后,还有谁来统接盘?
  20. 【计算机组成原理】:计算机系统概述

热门文章

  1. iOS持久化存储-CoreData简介
  2. NSWindowController的初始化创建代码
  3. 如何快糙好猛的使用Shiqi.Yu老师的公开人脸检测库(附源码)
  4. spring+mybatis实现读写分离
  5. svn服务器搭建-SuSE Linux Enterprise Server 11 SP3
  6. linear-gradient线性渐变
  7. Mysql中用SQL增加、删除字段,修改字段名、字段类型、注释,调整字段顺序总结...
  8. php 通过类名获取类的文件地址
  9. jquery纯数字验证
  10. java 算法优化向导