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. vc6.0创建文件时,出现很多烫烫烫解决方法

    vc6.0创建文件时,出现很多烫烫烫烫解决方法 SWM2烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫.IA 如果项目文件放在桌面上 如:C:\Documents and Settings\txwtec ...

  2. 团队Github实践训练

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 团队名称 WeChair 这个作业要求在哪里 团队Github实践训练 这个作业的目标 通过github实现团队协作编码 作业正 ...

  3. 多语言工作者の十日冲刺<9/10>

    这个作业属于哪个课程 软件工程 (福州大学至诚学院 - 计算机工程系) 这个作业要求在哪里 团队作业第五次--Alpha冲刺 这个作业的目标 团队进行Alpha冲刺--第九天(05.08) 作业正文 ...

  4. linux最小化安装命令补全

    bash-completion 需要安装bash-completion才能补全,安装后,重新打开一个窗口就能生效.

  5. Redis面试专题

    Redis面试专题 1. 什么是redis? Redis 是一个基于内存的高性能key-value数据库. (有空再补充,有理解错误或不足欢迎指正) 2. Reids的特点 Redis本质上是一个Ke ...

  6. .NETCore微服务探寻(二) - 认证与授权

    前言 一直以来对于.NETCore微服务相关的技术栈都处于一个浅尝辄止的了解阶段,在现实工作中也对于微服务也一直没有使用的业务环境,所以一直也没有整合过一个完整的基于.NETCore技术栈的微服务项目 ...

  7. 手把手教你使用Python抓取QQ音乐数据(第一弹)

    [一.项目目标] 获取 QQ 音乐指定歌手单曲排行指定页数的歌曲的歌名.专辑名.播放链接. 由浅入深,层层递进,非常适合刚入门的同学练手. [二.需要的库] 主要涉及的库有:requests.json ...

  8. Selenium+java - Edge浏览器启动

    写在前面 随着win10系统的普及,使得Edge浏览器得到广泛使用.从自动化角度看,自然微软也一直不断提供着支持服务,系统版本更新,对应的Edge浏览器版本也在更新,当然对应的驱动版本也会发生变化. ...

  9. 入门大数据---Flink开发环境搭建

    一.安装 Scala 插件 Flink 分别提供了基于 Java 语言和 Scala 语言的 API ,如果想要使用 Scala 语言来开发 Flink 程序,可以通过在 IDEA 中安装 Scala ...

  10. Scrapy框架简介及小项目应用

    今天来总结一下Scrapy框架的用法.scrapy的架构如下: Engine  :引擎,处理整个系统的数据流处理.触发事务,是整个框架的核心. Items :项目,它定义了爬取结果的数据结构,爬取的数 ...