ReentrantLock重入锁详解
1.定义
重入锁:能够支持一个线程对资源的重复加锁,也就是当一个线程获取到锁后,再次获取该锁时而不会被阻塞。
2.可重入锁的应用场景
2.1 如果已经加锁,则不再重复加锁,比如:交互界面点击后响应时间长,可能会多次点击,使用重入锁可防止后台重复执行。
if (lock.tryLock()) { //如果已经被lock,则立即返回false不会等待,达到忽略操作的效果
try {
//操作
} finally {
lock.unlock();
}
}
2.2 如果发现该操作已经在执行,则等待一段时间,超时则不再执行。
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) { //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false继续执行
try {
//操作
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
}
2.3 如果操作已经加锁,则等待一个一个加锁。可以防止资源使用冲突,保证同一个时间内只有一个操作可以使用该资源。
lock.lock(); //如果被其它资源锁定,会在此等待锁释放,也将处于阻塞状态
2.4 可中断锁:可响应中断操作,然后释放锁。
lock.lockInterruptibly();
3.原理分析
可重入锁在获取锁时,有公平和非公平获取锁两种方式,默认是后者。如果在绝对时间上,先对锁进行获取的请求先被满足,那么这个锁则是公平锁,也就是等待时间最长的线程优先获取锁。反之,则是非公平锁,非公平锁的效率较高。我们知道ReentrantLock
是通过使用自定义同步器来实现锁的获取和释放的,那么就看一下获取同步状态的代码。
3.1 非公平锁获取状态的代码
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
以上代码逻辑是这样的:当一个线程尝试获取锁时,先判断同步状态值是否为0,如果是,则该现场获取到锁,并返回true;如果同步状态值不为0,则判断该线程是否就是占用该锁的当前线程,如果是,则同步状态值增加,表示同一个线程多次加锁,并返回ture。否则,则获取同步状态值失败,返回false。
3.2 释放状态时的代码
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
在释放锁时,每释放一次,同步状态值就是减少,最终同步状态值为0时,表示完全释放锁,此时将占有线程设置为null,同时返回true。
3.3 公平锁获取状态时的代码
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
这和nonfairTryAcquire方法唯一的区别就是增加了hasQueuedPredecessors()方法,该方法的作用就是判断同步队列中当前节点是否有前驱节点。如果该方法返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能
继续获取锁。
现在有个问题需要思考,为什么非公平锁效率更高?
在nonfairTryAcquire(int acquires)方法中,当一个线程请求锁时,只要获取了同步状态即成功获取锁。在这个前提下,刚释放锁的线程再次获取同步状态的几率会非常大,使得其他线程只能在同步队列中等待。它虽然造成了线程饥饿,但是确实极少的线程切换,
保证了更达的吞吐量。而公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。
ReentrantLock重入锁详解的更多相关文章
- “全栈2019”Java多线程第二十九章:可重入锁与不可重入锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- 从源码角度彻底理解ReentrantLock(重入锁)
目录 1.前言 2.AbstractQueuedSynchronizer介绍 2.1 AQS是构建同步组件的基础 2.2 AQS的内部结构(ReentrantLock的语境下) 3 非公平模式加锁流程 ...
- java并发系列(四)-----源码角度彻底理解ReentrantLock(重入锁)
1.前言 ReentrantLock可以有公平锁和非公平锁的不同实现,只要在构造它的时候传入不同的布尔值,继续跟进下源码我们就能发现,关键在于实例化内部变量sync的方式不同,如下所示: /** * ...
- ReentrantLock(重入锁)以及公平性
ReentrantLock(重入锁)以及公平性 标签(空格分隔): java NIO 如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线 ...
- java高并发系列 - 第12天JUC:ReentrantLock重入锁
java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...
- ReentrantLock(重入锁)的源码解析
转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...
- Java多线程之ReentrantLock重入锁简介与使用教程
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html 我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...
- ReentrantLock 重入锁(下)
前沿: ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下Reentr ...
- ReentrantLock(重入锁)简单源码分析
1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申 ...
随机推荐
- 细说MVC中仓储模式的应用
文章提纲 概述要点 理论基础 详细步骤 总结 概述要点 设计模式的产生,就是在对开发过程进行不断的抽象. 我们先看一下之前访问数据的典型过程. 在Controller中定义一个Context, 例如: ...
- [Linux] awk与posix字符集
awk posix字符集[:alnum:] 文字数字字符[:alpha:] 文字字符[:digit:] 数字字符[:graph:] 非空字符(非空格.控制字符)[:lower:] 小写字符[:cntr ...
- 结合JDK源码看设计模式——简单工厂、工厂方法、抽象工厂
三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的 ...
- 【eclipse】eclipse报错:the resource is not on the build path of a java project
最近在eclipse中,使用svn导入svn上的一个maven项目,但是导入后类的包并没有以源码包的方式显示,而是以普通文件包的方式显示出来,在对类进行F3等操作时就报错:“the resource ...
- 预计2019年发布的Vue3.0到底有什么不一样的地方?
摘要: Vue 3.0预览. 原文:预计今年发布的Vue3.0到底有什么不一样的地方? 作者:小肆 微信公众号:技术放肆聊 Fundebug经授权转载,版权归原作者所有. 还有几个月距离 vue2 的 ...
- Oracle11g: datetime
--上一月,上一年 select add_months(sysdate,-1) last_month,add_months(sysdate,-12) last_year from dual; --下一 ...
- OpenUDID 和 IDFA 比较
iOS标识符 现今来比较下,不同情况下两种标识符的变化情况.以下试验,每种标识符均分为存于Keychain和未存于Keychain的两种情况做比较. 广告标识符(IDFA-identifierForI ...
- MySQL 处理海量数据时的一些优化查询速度方法
查询速度慢的原因 1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O 吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存不足 5.网络速度慢 6 ...
- MongoDB 中的【加减乘除】运算
很多同学因为对MongoDB不熟悉,加之应用的不是很多,有时候会认为MongoDB数据库对一些功能不支持,或者认为支持不好.今天我们 演示一下 MongoDB对“加减乘除”的使用. 在MongoDB数 ...
- mysql备份与还原!
一.备份常用操作基本命令 1.备份命令mysqldump格式 格式:mysqldump -h主机名 -P端口 -u用户名 -p密码 –database 数据库名 > 文件名.sql 2.备份M ...