前言

如果一个代码块被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详解的更多相关文章

  1. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  2. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  3. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  4. Java String类详解

    Java String类详解 Java字符串类(java.lang.String)是Java中使用最多的类,也是最为特殊的一个类,很多时候,我们对它既熟悉又陌生. 类结构: public final ...

  5. 最新java数组的详解

    java中HashMap详解 http://alex09.iteye.com/blog/539545 总结: 1.就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java ...

  6. JAVA IO 类库详解

    JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...

  7. 转:Java HashMap实现详解

    Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1.    HashMap概述:    HashMap是基于哈希表的M ...

  8. 淘宝JAVA中间件Diamond详解(2)-原理介绍

    淘宝JAVA中间件Diamond详解(二)---原理介绍 大家好,通过第一篇的快速使用,大家已经对diamond有了一个基本的了解.本次为大家带来的是diamond核心原理的介绍,主要包括server ...

  9. 【转】 java中HashMap详解

    原文网址:http://blog.csdn.net/caihaijiang/article/details/6280251 java中HashMap详解 HashMap 和 HashSet 是 Jav ...

随机推荐

  1. 如何将 JavaScript 代码添加到网页中,以及 <script> 标签的属性

    Hello, world! 本教程的这一部分内容是关于 JavaScript 语言本身的. 但是,我们需要一个工作环境来运行我们的脚本,由于本教程是在线的,所以浏览器是一个不错的选择.我们会尽可能少地 ...

  2. zookeeper集群部署问题排查记录

    今天在三台虚拟机搭建zookeeper集群,一直连不通,然后进行了几个小时的斗争,做个记录. 具体部署方式网上有很多, 不在赘述.产生连接不同的问题主要有以下几个方面: 1.仔细检查配置文件. 是否有 ...

  3. python-re正则表达--持续更新

    | 模式          | 描述| |----              |----| | \w            | 匹配字母数字及下划线 | | \W           | 匹配非字母数 ...

  4. Unity3D_07_日志、文本打印

    1.Debug.Log(“hello”); 2.打开控制台查看日志:ctrl+shift+c 3.输出一个位置的坐标(需要转换成字符串.ToString()) Vector3 worldPositio ...

  5. idea报错 Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource

    核对一下控制器是不是写了相同的路径...org.springframework.beans.factory.BeanCreationException: Error creating bean wit ...

  6. HBase的表结构

    HBase以表的形式存储数据.表有行和列组成.列划分为若干个列族/列簇(column family).  如上图所示,key1,key2,key3是三条记录的唯一的row key值,column-fa ...

  7. 手撸基于swoole 的分布式框架 实现分布式调用(20)讲

    最近看的一个swoole的课程,前段时间被邀请的参与的这个课程 比较有特点跟一定的深度,swoole的实战教程一直也不多,结合swoole构建一个新型框架,最后讲解如何实现分布式RPC的调用. 内容听 ...

  8. mysql 函数 存取过程

    1.打开数据库 2.选择函数,点击新建函数,选择过程,点击完成 4.写入自己要添加的sql语句 5.点击CTAL + S 保存,如若报错则语法或函数错误

  9. 05:videoToolbox:硬解码

    videoToolbox:硬解码 前言:VTDecompressionSession 工作流程: 1:创建解压的会话. 2:配置会话属性. 3:解压视频帧数据. 4:释放会话.释放资源. 介绍  VT ...

  10. 将字符串转换成json格式

    1.引入json依赖,在pom.xml文件里添加如下内容 <!--Json array start --> <dependency> <groupId>common ...