前言

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. SpringBoot 集成Swagger2自动生成文档和导出成静态文件

    目录 1. 简介 2. 集成Swagger2 2.1 导入Swagger库 2.2 配置Swagger基本信息 2.3 使用Swagger注解 2.4 文档效果图 3. 常用注解介绍 4. Swagg ...

  2. 解决Vue-cli3.0下scss文件编译过慢、卡顿问题

    在使用Vue-cli 3.0构建的项目中,可能存在项目编译过慢的问题,具体表现在编译时会在某一进度比如40%时停顿,等好一会儿才能够编译完成.这使得浏览器中的实时预览也会卡顿,不利于我们快速查看效果, ...

  3. 从零开始配置webpack(基于webpack 4 和 babel 7版本)

    webpack 核心概念: Entry: 入口 Module:模块,webpack中一切皆是模块 Chunk:代码库,一个chunk由十多个模块组合而成,用于代码合并与分割 Loader:模块转换器, ...

  4. codeforce 1311 C. Perform the Combo 前缀和

    You want to perform the combo on your opponent in one popular fighting game. The combo is the string ...

  5. poj1251 Jungle Roads Kruskal算法+并查集

    时限: 1000MS   内存限制: 10000K 提交总数: 37001   接受: 17398 描述 热带岛屿拉格里山的首长有个问题.几年前,大量的外援花在了村庄之间的额外道路上.但是丛林不断地超 ...

  6. Vxlan L2

    VXLAN(Virtual eXtensible LAN可扩展虚拟局域网)诞生了,基于IP网络之上,采用的是MAC in UDP技术 跨三层实现二层通信 总结为何需要Vxlan: 虚拟机规模受到网络规 ...

  7. Scrapy爬虫框架基本使用

    scrapyhub上有些视频简单介绍scrapy如何学习的(貌似要FQ):https://helpdesk.scrapinghub.com/support/solutions/articles/220 ...

  8. Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想

    Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 目录 Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 0x00 摘要 0x01 Flink 是什么 ...

  9. kafka-eagle监控kafka

    最近想做一个kafka监控,本来准备用zabbix来监控的,需要重复造轮子,本来准备用kafka-Manager的,在GitHub上无意发现了kafka-eagle,看了官方介绍准备试一下..... ...

  10. 不可错过的java面试博客之java集合篇

    1. List List 是有序的 Collection.Java List 一共三个实现类: 分别是 ArrayList.Vector 和 LinkedList ArrayList ArrayLis ...