Java 多线程 重入锁
作为关键字synchronized的替代品(或者说是增强版),重入锁是synchronized的功能扩展。在JDK 1.5的早期版本中,重入锁的性能远远好于synchronized,但从JDK 1.6开始,JDK优化了synchronized,使两者性能差距不大。重入锁使用java.util.concurrent.locks.ReentrantLock类来实现。
使用重入锁可以指定何时加锁和何时释放锁,对逻辑控制的灵活性远远好于synchronized,退出临界区时必须释放锁。之所以称为重入锁,是因为一个线程可以连续获得这种锁。而synchronized隐式地支持重进入。具体而言,重进入指线程在获取锁之后能再次获取该锁而不会被锁阻塞,该特性的实现需要解决两个问题:
1 线程再次获取锁
锁需要识别获取锁的线程是否为当前占有锁的线程,如果是,则再次成功获取。
2 锁的最终释放
线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求获取锁需要计数自增,计数表示锁被重复获取的次数,而锁被释放时计数自减,当计数为0时表示锁已经最终成功释放。
ReentrantLock通过组合自定义同步器实现锁的获取与释放,以非公平性(默认)实现为例,获取同步状态代码如下:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
再次获取同步方法的处理逻辑:通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则增加同步状态值并返回true,表示获取同步状态成功。也就是说,成功获取锁的线程再次获取锁,只是增加了同步状态值,这也要求ReentrantLock在释放同步状态时减少同步状态值,释放同步状态代码如下:
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;
}
如果锁被获取了n次,那么前n-1次tryRelease(int releases)方法必须返回false。也就是说,只有同步状态完全释放了,该方法才能返回true。该方法将同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。
简单的重入锁使用例子如下:
修改上述代码第7-12行:
在这种情况下,一个线程连续两次获得同一把锁。如果不允许该操作,那么同一个线程在第2次请求获得锁时,会和自己产生死锁,程序卡死在第2次申请锁的过程中。如果同一个线程多次获得锁,那么释放锁的次数与其相同。如果释放锁的次数多,那么会有java.lang.IllegalMonitorStateException异常;反之,如果释放锁的次数少了,那么该线程依然持有这个锁。
除了使用上的灵活性之外,重入锁还提供了一些高级功能:
1 中断响应
对于synchronized,如果一个线程在等待锁,那么结果只有两种可能:要么获得这把锁继续执行,要么保持等待。而使用重入锁可以提供另外一种可能:线程可以被中断。也就是说,线程在等待锁的过程中,程序可以根据需要取消对锁的请求。例如,小王与小明一起去打球,如果小王等了10分钟,小明还没到,接到小明的电话了解到由于突发情况不能如约了,那么小王就扫兴地打道回府了。对于线程而言,如果一个线程正在等待锁,那么它可以收到一个通知,被告知无需再等待,可以停止工作了。这种情况对于处理死锁是有一定帮助的。
2 锁申请等待限时
除了等待外部通知之外,避免死锁还有另外一种方法:限时等待。继续上面的例子,如果小王等了半个小时,小明还不来,那么小王就扫兴离去了。对于线程而言,如果给定一个等待时间,让线程自动放弃,那么可以避免线程持续等待。
3 公平锁
在大多数情况下,锁的申请都是非公平的。只要CAS设置同步状态成功,则表示当前线程获得了锁。也就是说,线程1首先申请了锁A,接着线程2也申请了锁A,那么当锁A可用时,哪个线程获得锁是不一定的,系统会从锁A的等待队列中随机选择一个,不能保证公平性。如同买票不排队,大家乱哄哄地围在售票窗口前,售票员忙得焦头烂额,顾不得谁先谁后,随便找个人出票就完事了。而公平锁保证锁的获取遵守FIFO原则即先到先得,不会产生饥饿现象,代价是大量的线程切换。synchronized产生的锁是非公平的,而重入锁可以设置是否公平。公平的重入锁要求系统维护一个有序队列,实现成本比较高,性能较低。所以,默认情况下的锁是非公平的。
ReentrantLock类常见方法如下:
1 public ReentrantLock(boolean fair)
当参数fair是true时,表示该重入锁是公平的。
2 lock()
获取锁,如果锁已被占用,则等待。
3 lockInterruptibly()
获取锁,但优先响应中断。
4 tryLock()
尝试获取锁,如果成功,返回true;否则,返回false。该方法不等待,立即返回。
5 tryLock(long time, TimeUnit unit)
在给定时间内尝试获取锁。
6 unlock()
释放锁。
重入锁的实现包含3个要素:
1 原子状态
原子状态使用CAS操作来存储当前锁的状态,判断锁是否已被其他线程持有。
2 等待队列
所有没有获取到锁的线程会进入等待队列进行等待。当有线程释放锁后,系统就从等待队列中唤醒一个线程,继续工作。
3 阻塞原语park()和unpark()
挂起和恢复线程,没有获取到锁的线程会被挂起。
参考资料
《实战Java高并发程序设计》 P71-80
《Java并发编程的艺术》 5.3 重入锁
Java 多线程 重入锁的更多相关文章
- 轻松学习java可重入锁(ReentrantLock)的实现原理
转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...
- java 可重入锁ReentrantLock的介绍
一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...
- 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)
前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...
- Java不可重入锁和可重入锁的简单理解
基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...
- Java可重入锁如何避免死锁
本文由https://bbs.csdn.net/topics/390939500和https://zhidao.baidu.com/question/1946051090515119908.html启 ...
- java可重入锁reentrantlock
public class ReentrantDemo { //重入锁 保护临界区资源count,确保多线程对count操作的安全性 /*public static ReentrantLock rtlo ...
- Java 可重入锁
一般意义上的可重入锁就是ReentrantLock http://www.cnblogs.com/hongdada/p/6057370.html 广义上的可重入锁是指: 可重入锁,也叫做递归锁,指的是 ...
- Java 可重入锁的那些事(一)
本文主要包含的内容:可重入锁(ReedtrantLock).公平锁.非公平锁.可重入性.同步队列.CAS等概念的理解 显式锁 上一篇文章提到的synchronized关键字为隐式锁,会自动获取和自动释 ...
- Java可重入锁与不可重入锁
可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的. synchronized 和 ReentrantLock 都是可重入锁. 可重入 ...
随机推荐
- 20165323 实验一 Java开发环境的熟悉
一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:杨金川 学号:20165323 指导教师:娄嘉鹏 实验日期:2018年4月2日 实验时间:13:45 - 15:25 实验序号:一 实 ...
- mysql查看工具——mysql profiler sql
http://www.profilesql.com/download/ 开发同学的福利--mysql监控工具sqlprofiler,类似sqlserver的profiler工具 https://www ...
- 浏览器LocalStroage使用
http://www.cnblogs.com/st-leslie/p/5617130.html
- Redis-Sentinel 哨兵
为什么需要哨兵? 一旦主节点宕机,那么需要人为修改所有应用方的主节点地址(改为新的master地址),还需要命令所有从节点复制新的主节点 那么这个问题,redis-sentinel就可以解决了 什么是 ...
- UICollectionViewController的用法1
UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableVie ...
- IIS:另一个程序正在使用此文件进程无法访问。
启动网站时,遇到这个错误,一般是端口已经被占用,更换一个空闲端口即可. 通过以下命令可查询 根据最后一列的数字在任务管理器中可查看被哪个程序占用了
- go-无法下载websocket的问题
由于限制问题,国内使用 go get 安装 golang 官方包可能会失败,如我自己在安装 collidermain 时,出现了以下报错: $ go get collidermain package ...
- 12px以下字体显示问题
刚接到广告公司出的设计稿,里面很多内容均是12px以下得字体,现在来总结一下解决办法,方便以后使用 1.使用png图片 但是会影响页面响应速度 2.使用transform: scale(0.x); 注 ...
- 做生活的有心人——xxx系统第一阶段总结
2017秋,桃子已经步入大学三年级了,觉得格外幸运 因为现在,有了学习的动力. 如果你和我一样也是在大学中后部分才意识到,自己是个大人了,思维模式开始转变开始融入一些前所未有的认知,觉得自己渺小得如沧 ...
- SSL/TLS
為 授权计算机为 SSL/TLS 安全通道建立信任关系. ServicePointManager.ServerCertificateValidationCallback += (o, c, ch, e ...