ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助。

前提条件

在理解ReentrantLock时需要具备一些基本的知识

理解AQS的实现原理

之前有写过一篇《深入浅出AQS源码解析》关于AQS的文章,对AQS原理不了解的同学可以先看一下

什么是可重入锁

当一个线程已经持有锁,如果该现在再次获取锁,是否可以获取成功?如果能获取成功则说明该锁是可重入的,否则是不可重入的

什么是公平锁和非公平锁

公平与非公平的一个很本质的区别就是,是否遵守FIFO(也就是先来后到)。当有多个线程来申请锁的时候,是否先申请的线程先获取锁,后申请的线程后获取锁?如果是的,则是公平锁,否则是非公平锁

更准确地说,先申请锁的线程先获得锁竞争的权利。对于公平的排他锁而言,先申请锁的线程会先获取锁,但是对于公平的共享锁而言,先申请锁的线程会先拥有获取锁竞争的权利,其他等待共享锁的线程也会被唤醒,有可能后唤醒的线程先获取锁。

ReentrantLock 源码解析

ReentrantLock的功能主要是通过3个内部类SyncFairSyncNonfairSync来实现的,这3个内部类继承了AbstractQueuedSynchronizer,其中FairSyncNonfairSync类继承了Sync,接下来我们一一解读这几个内部类。

ReentrantLock.Sync类源码解析

由于ReentrantLock.Sync类中的核心代码比较少,原理也比较简单,所以就直接在代码中通过详细注释的方式来解读

abstract static class Sync extends AbstractQueuedSynchronizer {

    /**
* 定义了一个抽象方法,用来获取锁
*/
abstract void lock(); /**
* NonfairSync中tryAcquire和、ReentrantLock.tryLock会使用到
* 重要功能:快速尝试获取锁,如果能够获取锁返回true,否则返回false
* 在尝试获取锁的过程中,不会阻塞当前线程,一般情况下是当前线程已经持有锁时
* 才有可能是可以直接获取锁,这也是可重入功能的核心实现
*/
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
/**
* state是AQS对外提供的一个变量,让不同的实现类可以通过这个变量
* 来控制锁被线程获取锁的次数
*/
int c = getState();
// 当state为0表示该锁是没有被任何线程持有
if (c == 0) {
/**
* CAS操作如果成功,说明当前线程竞争到了锁资源,
* 否则被其他线程竞争到了,当前线程需要进入AQS的同步队列
* 对于尝试修改state的值的线程可以同时是多个,
* 他们之间没有先后顺序,这也是非公平的重要体现
*/
if (compareAndSetState(0, acquires)) {
/**
* 当前线程已经持有锁了,设置锁的占有者
*/
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 如果持有锁的线程是当前线程,可以继续尝试获取锁
* 这也是可重入的重要体现
*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
/**
* state是int类型,也就是可重入次数不能低于Integer.MAX_VALUE
*/
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
/**
* 获取锁以后直接设置state的值
*/
setState(nextc);
return true;
}
/**
* 如果一个线程既不是第一次获取锁,又不是已经获取锁,
* 则该线程无法获取锁,需要进入AQS的同步队列排队
*/
return false;
} protected final boolean tryRelease(int releases) {
/**
* 计算释放releases个资源后state的值
*/
int c = getState() - releases;
/**
* 持有锁的线程如果不是当前线程,无法释放资源
*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
/**
* 当所有的资源全部释放掉(c=0)时,锁的持有者需要设置为null,
* 让后续线程可以来竞争锁
*/
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
/**
* 修改state的状态
*/
setState(c);
return free;
} protected final boolean isHeldExclusively() {
/**
* 当前线程是否持有锁
*/
return getExclusiveOwnerThread() == Thread.currentThread();
}

ReentrantLock.NonfairSync类源码解析

static final class NonfairSync extends Sync {
/**
* 非公平锁,对外获取锁的步骤:
* 首先,尝试修改state的状态(从0修改成1),如果修改成功说明当前没有任何线程持有锁
* 如果线程获取到锁,则把锁的持有线程设置为当前线程
* 如果无法获取锁,说明锁已经被线程持有,有两种情况:
* 情况1:持有锁的线程是当前线程,可以走可重入的流程
* 情况2:持有锁的线程不是当前线程,需要进入AQS去排队
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} /**
* 尝试快速获取锁
*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

ReentrantLock.FairSync类源码解析

static final class FairSync extends Sync {
/**
* 阻塞方式获取锁
*/
final void lock() {
acquire(1);
} /**
* 尝试获取公平锁,与上面分析的nonfairTryAcquire方法很类似,
* 重点描述彼此之间的区别
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/**
* 公平锁与非公平锁很大的一个区别是:
* 在尝试获取锁的时候,如果AQS的同步队列中有其他线程在等待获取锁
* 则尝试获取锁失败,需要进入AQS的同步队列排队
* hasQueuedPredecessors方法判断AQS的同步队列是否有线程在等待
*/
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;
}
}

ReentrantLock类源码解析

ReentrantLock类的实现方式比较简单,主要是依靠NonfairSyncFairSync实现的功能

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

    /**
* 默认是非公平锁
*/
public ReentrantLock() {
sync = new NonfairSync();
} /**
* 获取锁,获取的时候申请1个资源
*/
public void lock() {
sync.lock();
} /**
* 可中断的方式获取锁
*/
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
} /**
*
* 尝试获取锁,公平锁和非公平锁都是直接去尝试获取锁
* 一般在使用该方法的时候,如果尝试获取锁失败,会有后续操作,
* 可能是直接调用lock以阻塞的方式来获取锁
*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
} /**
* 带有超时时间的方式尝试获取锁
*/
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
} /**
* 释放锁,释放掉1个资源
*/
public void unlock() {
sync.release(1);
}
}

小结

  • 对于已经持有锁的线程,优先申请到资源
  • 对与没有持有锁的线程,需要等待持有锁的线程释放掉所有资源,包括可重入时申请到的资源
  • 公平锁在申请资源的时候要先检查AQS同步队列中是否有等待的线程,也就线程获取锁是按照FIFO的方式

深入浅出ReentrantLock源码解析的更多相关文章

  1. 深入浅出ReentrantReadWriteLock源码解析

    读写锁实现逻辑相对比较复杂,但是却是一个经常使用到的功能,希望将我对ReentrantReadWriteLock的源码的理解记录下来,可以对大家有帮助 前提条件 在理解ReentrantReadWri ...

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

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

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

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

  4. ReentrantLock源码解析

    ReentrantLock 1 数据结构 从上图可以看出,ReentrantLock的功能都是通过sync这个对象提供的. public class ReentrantLock implements ...

  5. 深入浅出Semaphore源码解析

    Semaphore通过permits的值来限制线程访问临界资源的总数,属于有限制次数的共享锁,不支持重入. 前提条件 在理解Semaphore时需要具备一些基本的知识: 理解AQS的实现原理 之前有写 ...

  6. Java并发之ReentrantLock源码解析(二)

    在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...

  7. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...

  8. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  9. ReentrantLock源码解析——虽众但写

    在看这篇文章时,笔者默认你已经看过AQS或者已经初步的了解AQS的内部过程.   先简单介绍一下ReentantLock,跟synchronized相同,是可重入的重量级锁.但是其用法则相当不同,首先 ...

随机推荐

  1. mybatis的嵌套查询与嵌套结果查询的不同

    原文:https://blog.csdn.net/qq_39706071/article/details/85156840 实体类: 嵌套查询mapper方法:嵌套查询的弊端:即嵌套查询的N+1问题尽 ...

  2. 这一次搞懂SpringMVC原理

    @ 目录 前言 正文 请求入口 组件初始化 调用Controller 参数.返回值解析 总结 前言 前面几篇文章,学习了Spring IOC.Bean实例化过程.AOP.事务的源码和设计思想,了解了S ...

  3. Linux nohup命令详解,终端关闭程序依然可以在执行!

    大家好,我是良许. 在工作中,我们很经常跑一个很重要的程序,有时候这个程序需要跑好几个小时,甚至需要几天,这个时候如果我们退出终端,或者网络不好连接中断,那么程序就会被中止.而这个情况肯定不是我们想看 ...

  4. git常用代码合集

    git常用代码合集 1. Git init:初始化一个仓库 2. Git add 文件名称:添加文件到Git暂存区 3. Git commit -m “message”:将Git暂存区的代码提交到Gi ...

  5. chromedp入门

    chromedp入门 chromedp是什么? chromedp是go写的,支持Chrome DevTools Protocol 的一个驱动浏览器的库.并且它不需要依赖其他的外界服务(比如 Selen ...

  6. linux 系统文件目录颜色及特殊权限对应的颜色

    什么决定文件目录的颜色和背景?  颜色  说明  栗子  权限 白色 表示普通文件   蓝色 表示目录  绿色 表示可执行文件 浅蓝色 链接文件 黄色 表示设备文件 红色   表示压缩文件 红色闪烁 ...

  7. Maximum Subsequence Sum(java)

    7-1 Maximum Subsequence Sum(25 分) Given a sequence of K integers { N​1​​, N​2​​, ..., N​K​​ }. A con ...

  8. MyBatis学习笔记(2)--缓存

    一.什么是缓存 --存在于内存中的临时数据. 为什么使用缓存?--减少和数据库的交互次数,提高执行效率. 适用于缓存的数据: 1.经常查询并且不经常改变的数据. 2.数据的正确与否对最终结果影响较小的 ...

  9. 关于idea的一些快捷键

    最近在用idea写代码,熟悉一些快捷键的使用能够让写代码的速度提高,以下快捷键是默认idea的快捷键,当然我们可以自己修改的: 自动补全代码快捷键:CTRL+alt+V 自动格式化代码:CTRL+al ...

  10. vue-elemnt-admin源码学习

    vue-elemnt-admin源码学习 vue-element-admin是一个基于vue,element-ui的集成的管理后台.它的安装部分就不说了,按照官网的步骤一步步就可以执行了. https ...