前言

ReentrantLock是JUC提供的可重入锁的实现,用法上几乎等同于Synchronized,但是ReentrantLock在功能的丰富性上要比Synchronized要强大。

一、ReentrantLock的使用

ReentrantLock实现了JUC中的Lock接口,Lock接口定义了一套加锁和解锁的方法,方法如下:

     /**
* 加锁,如果加锁失败则会阻塞当前线程,直到加锁成功
*/
void lock(); /**
* 同上,不过会响应中断,当线程设置中断时会抛异常退出
*/
void lockInterruptibly() throws InterruptedException; /**
* 尝试加锁,不会阻塞当前线程,加锁失败则直接返回false,成功则返回true
*/
boolean tryLock(); /**
* 同上,不过有超时时间,当直到时间之后还是没有加锁成功,则返回false,成功则返回true
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; /**
* 解锁
*/
void unlock(); /**
* 创建Condition对象,用于实现线程的等待/唤醒机制
*/
Condition newCondition();

ReentrantLock使用案例如下:

         ReentrantLock lock = new ReentrantLock(true);//初始化Lock对象
lock.lock();//加锁操作
try{
//TODO do someThing
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁操作
}

ReentrantLock的使用比较简单,直接通过构造函数创建实例,分别调用lock方法加锁,unlock方法解锁即可。

ReentrantLock的构造方法有两个分别如下:

 /**默认构造函数,默认采用非公平锁*/
public ReentrantLock() {
sync = new NonfairSync();
} /**传入fair字段表示是否采用公平锁,true为公平锁;false为非公平锁*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock支持公平锁和非公平锁两种锁机制,公平锁则表示同步的队列是FIFO模式的,等待时间最长的线程先获取锁;非公平模式则表示获取锁的线程完全随机,看CPU分配给哪个线程就由哪个线程获取锁。

二、ReentrantLock的实现原理解析

ReentrantLock的实现原理全部是通过其内部类Sync实现的,Sync集成于AQS并重写了AQS的获取和释放同步状态的方法,源码如下:

Reentrantock的加锁和解锁方法都是调用了内部类Sync的对应方法

 /**加锁方法*/
public void lock() {
sync.lock();
} /**尝试加锁方法*/
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
} /**解锁方法*/
public void unlock() {
sync.release(1);
} /**创建Condition对象*/
public Condition newCondition() {
return sync.newCondition();
} /**判断当前线程是否独占锁*/
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}

所以探究ReentrantLock的实现原理,主要是看内部类Sync的实现逻辑,而ReentrantLock类中除了有内部类Sync,还有Sync的两个子类(公平同步器)FairSync和(非公平同步器)NonFairSync,Sync的子类分别重写了Sync的lock方法和tryAcquire方法,

FairSync实现的是公平锁的效果,NonFairSync实现的是非公平锁的效果。

公平锁实现源码:

 /** class FairSync * /

 /**公平锁*/
final void lock() {
acquire(1);//调用AQS的acquire方法
}

AQS的acquire实际是调用了子类的tryAcquire方法,FairSync的tryAcquire方法源码如下:

 /**公平加锁*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//获取当前线程
int c = getState();//获取当前同步状态
if (c == 0) {//当状态为0时表示锁没有被占有
/**
* 尝试获取锁
* hasQueuedPredecessors()方法判断当前线程是否是head节点的后继节点
* compareAndSetState()通过CAS来设置AQS的state值
* */
if (!hasQueuedPredecessors() &&
12 compareAndSetState(0
, acquires)) {
/**设置当前线程为占用锁的线程*/
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 当状态不为0时,表示锁已经被占用,此时判断当前线程是否是占用锁的线程
* getExclusiveOwnerThread()方法返回当前占用锁的线程
* */
else if (current == getExclusiveOwnerThread()) {
/**
* 如果当前线程已经占有了锁,则修改状态+1,表示占用了锁次数+1
* 所以可重入锁的实现原理就是state值 + 1
* */
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
/**设置同步状态值 = 旧状态值 + 1*/
setState(nextc);
return true;
}
return false;
}

从源码看出,公平锁的实现逻辑实际就是遵循FIFO原则,尝试获取锁的前提是必须当前线程是同步队列head节点的后继节点,这样就保证了获取锁的顺序是完全按照同步队列的节点顺序获取的。

非公平锁实现原理:

     /**非公平锁加锁*/
final void lock() {
/**直接通过CAS尝试设置同步状态,
* 成功则调用setExclusiveOwnerThread方法设置当前线程为占用锁的线程*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
/**失败则调用AQS的acquire方法,实际是调用tryAcquire方法*/
acquire(1);
} protected final boolean tryAcquire(int acquires) {
/**调用Sync的nonfairTryAcquire方法*/
return nonfairTryAcquire(acquires);
} /**非公平获取同步状态*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/**
* 判断当前同步状态值,值为0则表示可以获取锁
* 直接通过CAS设置同步状态,成功则获取锁成功
* */
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
/**
* 当锁被占用后判断是否占用锁的线程是否是当前线程
* 如果是则状态值+1,表示锁可以重入
* */
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

通过源码可以看出非公平锁和加锁流程和公平锁的加锁流程基本上一致,只是公平锁加锁之前需要判断当前线程是否是同步队列head节点的后继节点,而非公平锁无需判断直接可以尝试加锁。所以效率上非公平锁的效率更高。

解锁源码解析:

 /**ReentrantLock的解锁方法直接调用Sync的解锁方法
* release()是父类AQS的方法,实际是调用了子类的tryRelease方法*/
public void unlock() {
sync.release(1);
} /**Sync重写了AQS的tryRelease方法*/
protected final boolean tryRelease(int releases) {
/**1. 获取同步状态 -1 表示释放一次锁*/
int c = getState() - releases;
/**2. 判断当前线程是否是当前占有锁的线程*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
/**3. 如果同步状态值为0,表示锁完全释放了,则清除当前线程为占有锁的线程
* 如果同步状态值不为0,则表示加锁的次数多于解锁的次数,还需要继续解锁*/
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
/**设置同步状态值为最新的状态值*/
setState(c);
return free;
}

可以看出释放锁时就是将同步状态的值减1,直到同步状态的值变成0才表示完全释放成功,否则都会返回false表示释放锁失败。

三、ReentrantLock和Synchronzied的相同点和不同点?

相同点:

1、可重入锁,ReentrantLock和Synchronzied都是可重入锁,获取锁的线程都可以重复获取锁成功

2、独占式锁,ReentrantLock和Synchronzied都是独占式锁,同一时刻都只允许一个线程获取锁

不同点:

1、Synchronzied可重入是隐式的,解锁是自动释放的,释放之前都可以重入;ReentrantLock可重入是显式的,需要执行多次加锁和多次释放锁操作,且加锁和解锁的次数需要完全一致,否则可能会导致其他线程无法获取到锁。

2、Synchronized是非公平锁,多线程竞争锁成功与否看各个线程自行争取;ReentrantLock同时支持公平锁和非公平锁,默认是非公平锁和Synchronzied一样,而公平锁就遵循FIFO原则,先进入等待队列中的线程优先获取锁

3、Synchronzied不需要手动释放锁;ReentrantLock需要手动释放锁,如果不释放其他线程就无法获取锁,所以释放锁需要放在finally中执行

4、Synchronzied不可响应中断,获取不到就会一直阻塞直到获取锁成功;ReentrantLock支持响应中断,可以通过设置中断标识来中断阻塞的线程

5、Synchronzied是通过获取对象的Monitor对象来实现的;ReentrantLock是通过AQS的子类来实现的

四、ReentrantLock注意事项?

1、ReentrantLock的可重入性是显式的

ReentrantLock可重入性实际就针对同步状态自增或自减操作,重入了多少次锁就必须对应的释放多少次锁,而不可以进多次而只释放一次,只有当前释放次数和加锁次数一样时才算真正的释放成功。

2、公平锁和非公平锁比较

公平锁遵循FIFO原则保证了获取锁的顺序和同步队列中的线程顺序一致,但是性能上比非公平锁差很多,因为需要不停的CPU线程切换。

非公平锁性能更好,没有多余的CPU线程切换消耗,但是极端情况下会出现“饥饿线程”问题(某些线程始终抢不到锁而一致等待着)

Java并发包4--可重入锁ReentrantLock的实现原理的更多相关文章

  1. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  2. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  3. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  4. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  5. Java 显示锁 之 重入锁 ReentrantLock(七)

    ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...

  6. synchronized关键字,Lock接口以及可重入锁ReentrantLock

    多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...

  7. 17_重入锁ReentrantLock

    [概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...

  8. Java中可重入锁ReentrantLock原理剖析

    本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...

  9. Java多线程——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...

随机推荐

  1. 接近8000字的Spring/SpringBoot常用注解总结!安排!

    0.前言 大家好,我是 Guide 哥!这是我的 221 篇优质原创文章.如需转载,请在文首注明地址,蟹蟹! 本文已经收录进我的 75K Star 的 Java 开源项目 JavaGuide:http ...

  2. 基于规则的分类——RIPPER算法

    在<分类:基于规则的分类技术>中已经比较详细的介绍了基于规则的分类方法,RIPPER算法则是其中一种具体构造基于规则的分类器的方法.在RIPPER算法中,有几个点是算法的重要构成部分,需要 ...

  3. Linux运维面试题:请简要说明Linux系统在目标板上的启动过程?

    Linux运维面试题:请简要说明Linux系统在目标板上的启动过程? 该问题是Linux运维面试最常见的问题之一,问题答案如下: 1.用户打开PC的电源,BIOS开机自检,按BIOS中设置的启动设备( ...

  4. RHCS图形界面建立GFS共享下

    我们上面通过图形界面实现了GFS,我们这里使用字符界面实现 1.1.       系统基础配置 5台节点均采用相同配置. 配置/etc/hosts文件 # vi /etc/hosts 127.0.0. ...

  5. db2 锁表

    2019独角兽企业重金招聘Python工程师标准>>> 查询锁表情况 db2 => get snapshot for locks on databasename 可以看到什么表 ...

  6. 日常开发中常用的linux命令

    本文并不将linux的常用命令全部罗列出来,列出一下常用.容易忘记的命令. 更详细的说明见:https://www.cnblogs.com/xuxinstyle/p/9609551.html 文件相关 ...

  7. 题目分享D 二代目

    题意:给定一个T条边的无向图,求S到E恰好经过N条边的最短路径 T≤100 N≤1000000 分析:(据说好像假期学长讲过) 首先很容易想到的是dp[i][j][k]表示从i到j经过k条边的最短路径 ...

  8. windows脱密码总结

    方式1:通过SAM数据库获得本地用户HASH sam文件:是用来存储本地用户账号密码的文件的数据库system文件:里面有对sam文件进行加密和加密的密钥 利用方式: 导出sam和system: re ...

  9. Hadoop入门学习笔记-第三天(Yarn高可用集群配置及计算案例)

    什么是mapreduce 首先让我们来重温一下 hadoop 的四大组件:HDFS:分布式存储系统MapReduce:分布式计算系统YARN: hadoop 的资源调度系统Common: 以上三大组件 ...

  10. 《Vue.js 2.x实践指南》 已出版

    <Vue.js 2.x实践指南>其实在一年前就已经完稿了,只是由于疫情的缘故耽搁了很久才下厂印刷.阅读本书需要具备HTML.CSS和JS基础,本书针对的用户群体主要是:想要快速学习vue技 ...