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 都是可重入锁. 可重入 ...
随机推荐
- Aho-Corasick算法实现(简单关键字过滤)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Cons ...
- 移动端开发demo—移动端web相册(一)
本文主要是介绍开发移动端web相册这样一案例用到的前置知识. 一.移动端样式 移动端更接近手机原生的方式. 如下是一个angular mobile的demo的例子: 移动端demo做成这样的好处: 在 ...
- T37302 P哥的桶
题解: 比较简单的一道题 线段树+线性基 显然离线处理出位置 然后线段树updata的时候暴力合并线性基 nlogn^3 一个常数优化就是线性基已满就直接返回这个线性基 还有个优化是用快速找到第一个1 ...
- mysql基本操作(一)
1.登录mysql mysql -h localhost -u root -p 登录mysql,其中 -h是指定要连接mysql服务器的主机名 -u是指定用户 -次数登录必须用-p输入密 ...
- 【Android】application标签说明
<application> <application android:allowClearUserData=["true" | "false" ...
- Spring Boot Starters 列表
Spring Boot application starters 名称 描述 Pom spring-boot-starter 核心starter,包括自动配置支持,日志和YAML Pom spring ...
- java添加多个水印
package com.zhx.util.imgutil; import com.zhx.util.stringutil.ArithUtil; import net.coobird.thumbnail ...
- DDoS攻击与防御(2)
2.攻击系统资源终端设备在与服务器进行通信时,经常需要创建会话连接,在此过程中通常会使用TCP和SSL等协议.会话连接一旦被占满,新进入的会话请求就必须等待前面的会话完成.消耗系统资源的DDoS攻击的 ...
- Dev-C++安装第三方库boost
Dev-C++安装第三方库boost 转 https://www.jianshu.com/p/111571e4d6f5?utm_source=oschina-app 之前鉴于codeblocks界面 ...
- python之psutil模块(获取系统性能信息(CPU,内存,磁盘,网络)
一.psutil模块 1. psutil是一个跨平台库(http://code.google.com/p/psutil/),能够轻松实现获取系统运行的进程和系统利用率(包括CPU.内存.磁盘.网络等) ...