java之ReentrantLock详解
前言
如果一个代码块被synchronized修饰了,当一个线程获取了相应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的释放,现在有这么一种情况,这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程只能干巴巴地等着,在这种情况下,非常影响程序执行效率
所以Lock应运而生,可以不让等待的线程一直等待下去(比如只等待一定的时间或者能够响应中断)
一、Lock接口
(1)与synchronzed区别
synchronized是JVM层面的内置锁,而Lock则是java层面的显示锁,Lock提供了一种可重入的、可轮询的、定时的以及可中断的锁获取操作。
采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
(2)lock接口源码详解
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
Condition newCondition();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}
lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。 newCondition()方法返回一个Condition对象
lock():此方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
tryLock():此方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
tryLock(long time, TimeUnit unit):此方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
二、ReentrantLock
(1)模拟可中断的锁获取
Lock中的lockInterruptibly() 可以在获得锁的同时保持对中断的响应,但是内置锁synchronized却很难实现这个功能。
synchronized
如下程序,创建一任务,假设该任务需要执行很长时间才能结束(使用死循环来模拟时长)。现在有两个线程竞争该资源的内置锁,在等待一段时间后,想要终止线程t2的锁获取等待操作,使用t2.interrupt(); 尝试中断线程t2。遗憾的是,此时t2根本不会响应这个中断操作,它会继续等待直到获得资源锁。
public class InterruptedLockTest implements Runnable{
public synchronized void doCount(){
//使用死循环表示此操作要进行很长的一段时间才能结束
while(true){}
}
@Override
public void run() {
doCount();
}
}
public static void main(String[] args) throws InterruptedException {
InterruptedLockTest test = new InterruptedLockTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
//等待两秒,尝试中断线程t2的等待
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
//等待1秒,让 t2.interrupt(); 执行生效
TimeUnit.SECONDS.sleep(1);
System.out.println("线程t1是否存活:" + t1.isAlive());
System.out.println("线程t2是否存活:" + t2.isAlive());
}
console打印:
线程t1是否存活:true
线程t2是否存活:true
Lock
public class LockDemo implements Runnable {
@Override
public void run() {
try {
doCount();
} catch (InterruptedException e) {
System.out.println("被中断....");
}
}
Lock lock = new ReentrantLock();
public void doCount() throws InterruptedException {
lock.lockInterruptibly();
try {
while (true){
}
}catch (Exception e){
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
LockDemo lockDemo = new LockDemo();
Thread t1 = new Thread(lockDemo);
Thread t2 = new Thread(lockDemo);
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
//等待1秒,让 t2.interrupt(); 执行生效
TimeUnit.SECONDS.sleep(1);
System.out.println("线程t1是否存活:" + t1.isAlive());
System.out.println("线程t2是否存活:" + t2.isAlive());
}
}
console打印:
被中断....
线程t1是否存活:true
线程t2是否存活:false
(2)模拟可轮询(避免死锁)
相比于synchronized内置锁的无条件锁获取模式,Lock提供了tryLock() 实现可定时和可轮询的锁获取模式,这也使Lock具有更完善的错误恢复机制。在内置锁中,死锁是一个很严重的问题,造成死锁的原因之一可能是,锁获取顺序不一致导致程序死锁。比如说,线程1持有A对象锁,正在等待获取B对象锁;线程2持有B对象锁,正在等待获取A对象锁。这样,两个线程都会由于获取不到想要的锁而陷入死锁的境地。解决办法可以是,两个线程要么同时获取两个锁,要么一个锁都不获取。Lock 的可定时和可轮询锁就可以很好的满足该条件,从而避免死锁的发生
synchronized死锁问题
public class TestDeadLock implements Runnable{
int flag ;
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
if(flag==0){
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("flag==0");
}
}
}
else if (flag==1){
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("flag==1");
}
}
}
}
public static void main(String[] args) {
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 0;
td2.flag = 1;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}
ReentrantLock可轮询
// 资源类
public class Resource {
//资源总和
private int resourceNum;
// 显示锁
public Lock lock = new ReentrantLock();
public Resource(int resourceNum){
this.resourceNum = resourceNum;
}
//返回此资源的总量
public int getResourceNum(){
return resourceNum;
}
}
public class LockTest1 {
//传入两个资源类和预期操作时间,在此期间内返回两个资源的数量总和
public int getResource(Resource resourceA, Resource resourceB, long timeout, TimeUnit unit)
throws InterruptedException {
// 获取当前时间,算出操作截止时间
long stopTime = System.nanoTime() + unit.toNanos(timeout);
while(true){
try {
// 尝试获得资源A的锁
if (resourceA.lock.tryLock()) {
try{
// 如果获得资源A的锁,尝试获得资源B的锁
if(resourceB.lock.tryLock()){
//同时获得两资源的锁,进行相关操作后返回
return getSum(resourceA, resourceB);
}
}finally {
resourceB.lock.unlock();
}
}
}finally {
resourceA.lock.unlock();
}
// 判断当前是否超时,规定-1为错误标识
if(System.nanoTime() > stopTime)
return -1;
//睡眠1秒,继续尝试获得锁
TimeUnit.SECONDS.sleep(1);
}
}
// 获得资源总和
public int getSum(Resource resourceA,Resource resourceB){
return resourceA.getResourceNum()+resourceB.getResourceNum();
}
}
(3)公平锁
背景
CPU在调度线程的时候是在等待队列里随机挑选一个线程,由于这种随机性所以是无法保证线程先到先得的(synchronized控制的锁就是这种非公平锁)。但这样就会产生饥饿现象,即有些线程(优先级较低的线程)可能永远也无法获取cpu的执行权,优先级高的线程会不断的强制它的资源。那么如何解决饥饿问题呢,这就需要公平锁了。
公平锁可以保证线程按照时间的先后顺序执行,避免饥饿现象的产生。但公平锁的效率比较地,因为要实现顺序执行,需要维护一个有序队列。
// 也可以指定公平性
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//默认创建非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
Demo
package com.jalja.base.threadTest;
import java.util.concurrent.locks.ReentrantLock;
public class LockFairTest implements Runnable{
//创建公平锁
private static ReentrantLock lock=new ReentrantLock(true);
public void run() {
while(true){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"获得锁");
}finally{
lock.unlock();
}
}
}
public static void main(String[] args) {
LockFairTest lft=new LockFairTest();
Thread th1=new Thread(lft);
Thread th2=new Thread(lft);
th1.start();
th2.start();
}
}
console打印:
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
Thread-1获得锁
Thread-0获得锁
分析结果可看出两个线程是交替执行的,几乎不会出现同一个线程连续执行多次。
三、Synchronized与Lock的区别
锁类型
可重入锁:在执行对象中所有同步方法不用再次获得锁
可中断锁:在等待获取锁过程中可中断
公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利
读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
区别
| 类别 | synchronized | Lock |
|---|---|---|
| 存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
| 锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
| 锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
| 锁状态 | 无法判断 | 可以判断 |
| 锁类型 | 可重入 不可中断 非公平 | 可重入 可中断 可公平(两者皆可) |
| 性能 | 少量同步 | 大量同步 |
关于读写锁
我们知道,当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。
java之ReentrantLock详解的更多相关文章
- 最强Java并发编程详解:知识点梳理,BAT面试题等
本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
- Java 序列化Serializable详解
Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...
- Java String类详解
Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...
- 最新java数组的详解
java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...
- JAVA IO 类库详解
JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...
- 转:Java HashMap实现详解
Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1. HashMap概述: HashMap是基于哈希表的M ...
- 淘宝JAVA中间件Diamond详解(2)-原理介绍
淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...
- 【转】 java中HashMap详解
原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...
随机推荐
- 007 Python程序语法元素分析
目录 一.概述 二.程序的格式框架 2.1 代码高亮 2.2 缩进 2.3 注释 2.4 缩进.注释 三.命名与保留字 3.1 变量 3.2 命名 3.3 保留字 3.4 变量.命名.保留字 四.数据 ...
- springboot整合mybatis(注解)
springboot整合mybatis(注解) 1.pom.xml: <?xml version="1.0" encoding="UTF-8"?> ...
- SWPU CTF题解
本博客为西南石油大学(南充校区)CTF团队赛的题解 所有题目网址:http://47.106.87.69:9000/game 今天我是流泪狗狗头 解压后发现压缩包中是一个带有密码的图片,winhex分 ...
- Python--使用四种随机方法(Random)来产生随机价格
import random # 卖橘子的计算器:写一段代码,提示用户输入橘子的价格,# 然后随机生成购买的斤数(5到10斤之间),最后计算出应该支付的金额! # 第一种# orange_price = ...
- jmeter linux压测报错:Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'/home/server/ptest/disk_out.jmx'.
1.linux环境jmeter与win环境编写脚本的jmeter版本不一致,版本改为一致 2.脚本中存在中文,去除中文 3.脚本中存在类似于jp@gc - Active Threads Over Ti ...
- charles 视图菜单总结
本文参考:charles 视图菜单总结 Charles的视图菜单里的东西其实是非常常用的功能: 但是我们一般是不需要从这里点进来的: 里面,无非是查看的视图结构(按照域名和按照访问时间) 然后是一些概 ...
- elasticsearch Discovery 发现模块学习
发现模块和集群的形成 目标 发现节点 Master选举 组成集群,在Master信息发生变化时及时更新. 故障检测 细分为几个子模块 Discovery发现模块 Discover是在集群Master节 ...
- 基于 HTML5 WebGL 的医疗物流系统
前言 物联网( IoT ),简单的理解就是物体之间通过互联网进行链接.世界上的万事万物,都可以通过数据的改变进行智能化管理.ioT 的兴起在医疗行业中具有拯救生命的潜在作用.不断的收集用户信息并且实时 ...
- PiVot 用法
基本语法: SELECT <非透视的列>, [第一个透视的列] AS <列名称>, [第二个透视的列] AS <列名称>, ... [最后一个透视的列] AS &l ...
- filebeat使用multiline丢失数据问题
最近部署filebeat采集日志. 发现配置multiline后,日志偶尔会丢失数据,而且采集到的数据长度都不相同,所以和日志长度没有关系. 查阅filebeat官网后,找到了问题.filebeat有 ...