Java并发包4--可重入锁ReentrantLock的实现原理
前言
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的实现原理的更多相关文章
- 轻松学习java可重入锁(ReentrantLock)的实现原理
转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...
- 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)
前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...
- Java 重入锁 ReentrantLock 原理分析
1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...
- java 可重入锁ReentrantLock的介绍
一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...
- Java 显示锁 之 重入锁 ReentrantLock(七)
ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...
- synchronized关键字,Lock接口以及可重入锁ReentrantLock
多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...
- 17_重入锁ReentrantLock
[概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...
- Java中可重入锁ReentrantLock原理剖析
本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一. 概述 本文首先介绍Lock接口.ReentrantLock的类层次结构以及锁功能模板类AbstractQue ...
- Java多线程——深入重入锁ReentrantLock
简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...
随机推荐
- apollo 项目配置中心开源框架部署
apollo 于我带来的好处 1. 项目之前的配置信息全部都在 resources 目录下,当然这里我使用的是 Spring Boot 搭建的项目.使用 apollo 后,配置信息全部转移到 apol ...
- Javascript中的string类型使用UTF-16编码
2019独角兽企业重金招聘Python工程师标准>>> 在JavaScript中,所有的string类型(或者被称为DOMString)都是使用UTF-16编码的. MDN DOMS ...
- UVA10599:Robots(II)(最长上升子序列)
Your company provides robots that can be used to pick up litter from fields after sporting events an ...
- 怎么在Chrome和Firefox浏览器中清除HSTS设置?
HSTS代表的是HTTPS严格传输安全协议,它是一个网络安全政策机制,能够强迫浏览器只通过安全的HTTPS连接(永远不能通过HTTP)与网站交互.这能够帮助防止协议降级攻击和cookie劫持. HST ...
- RedHat Enterprise Linux 5.8 升级openssl
RedHat Enterprise Linux 5.8升级openssl,遇到以下问题,做下标记: 由于之前安装RedHat Enterprise Linux 5.8 时候只安装了服务器环境,没有安装 ...
- 03 Django下载和使用 三板斧httpresponse render redirect
简介 是一个为完美主义者设计的web框架 The web framework for perfectionists with deadlines. Django可以使你能够用更少的代码,更加轻松且快速 ...
- python sort和sorted的区别
sort 与 sorted 区别: sort 是应用在 list 上的方法,使用方法为 list.sort(),是对原有列表进行操作,改变原有列表的排序: sorted 可以对所有可迭代的对象进行排序 ...
- 自动化运维工具Ansible之Tests测验详解
Ansible Tests 详解与使用案例 主机规划 添加用户账号 说明: 1. 运维人员使用的登录账号: 2. 所有的业务都放在 /app/ 下「yun用户的家目录」,避免业务数据乱放: 3. 该用 ...
- Vue + Element-ui实现后台管理系统(4)---封装一个ECharts组件的一点思路
封装一个ECharts组件的一点思路 有关后台管理系统之前写过三遍博客,看这篇之前最好先看下这三篇博客.另外这里只展示关键部分代码,项目代码放在github上: mall-manage-system ...
- 【HBase】快速搞定HBase与Hive的对比、整合
目录 对比 整合 需求一 步骤 一.将HBase的五个jar包拷贝到Hive的lib目录下 二.修改hive的配置文件 三.在Hive中建表 四.创建hive管理表与HBase映射 五.在HBase中 ...