ReentrantLock 中的 4 个坑!

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

JDK 1.5 之前 synchronized 的性能是比较低的,但在 JDK 1.5 中,官方推出一个重量级功能 Lock,一举改变了 Java 中锁的格局。JDK 1.5 之前当我们谈到锁时,只能使用内置锁 synchronized,但如今我们锁的实现又多了一种显式锁 Lock。

前面的文章我们已经介绍了 synchronized,详见以下列表:

《synchronized 加锁 this 和 class 的区别!》

《synchronized 优化手段之锁膨胀机制!》

《synchronized 中的 4 个优化,你知道几个?》

所以本文咱们重点来看 Lock。

Lock 简介

Lock 是一个顶级接口,它的所有方法如下图所示:

它的子类列表如下:

我们通常会使用 ReentrantLock 来定义其实例,它们之间的关联如下图所示:

PS:Sync 是同步锁的意思,FairSync 是公平锁,NonfairSync 是非公平锁。

ReentrantLock 使用

学习任何一项技能都是先从使用开始的,所以我们也不例外,咱们先来看下 ReentrantLock 的基础使用:

public class LockExample {// 创建锁对象private final ReentrantLock lock = new ReentrantLock();public void method() {// 加锁操作lock.lock();try {// 业务代码......} finally {// 释放锁lock.unlock();}}
}

ReentrantLock 在创建之后,有两个关键性的操作:

  • 加锁操作:lock()

  • 释放锁操作:unlock()

ReentrantLock 中的坑

1.ReentrantLock 默认为非公平锁

很多人会认为(尤其是新手朋友),ReentrantLock 默认的实现是公平锁,其实并非如此,ReentrantLock 默认情况下为非公平锁(这主要是出于性能方面的考虑),比如下面这段代码:

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {// 定义线程任务Runnable runnable = new Runnable() {@Overridepublic void run() {// 加锁lock.lock();try {// 打印执行线程的名字System.out.println("线程:" + Thread.currentThread().getName());} finally {// 释放锁lock.unlock();}}};// 创建多个线程for (int i = 0; i < 10; i++) {new Thread(runnable).start();}}
}

以上程序的执行结果如下:

从上述执行的结果可以看出,ReentrantLock 默认情况下为非公平锁。因为线程的名称是根据创建的先后顺序递增的,所以如果是公平锁,那么线程的执行应该是有序递增的,但从上述的结果可以看出,线程的执行和打印是无序的,这说明 ReentrantLock 默认情况下为非公平锁。

想要将 ReentrantLock 设置为公平锁也很简单,只需要在创建 ReentrantLock 时,设置一个 true 的构造参数就可以了,如下代码所示:

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象(公平锁)private static final ReentrantLock lock = new ReentrantLock(true);public static void main(String[] args) {// 定义线程任务Runnable runnable = new Runnable() {@Overridepublic void run() {// 加锁lock.lock();try {// 打印执行线程的名字System.out.println("线程:" + Thread.currentThread().getName());} finally {// 释放锁lock.unlock();}}};// 创建多个线程for (int i = 0; i < 10; i++) {new Thread(runnable).start();}}
}

以上程序的执行结果如下:

从上述结果可以看出,当我们显式的给 ReentrantLock 设置了 true 的构造参数之后,ReentrantLock 就变成了公平锁,线程获取锁的顺序也变成有序的了。

其实从 ReentrantLock 的源码我们也可以看出它究竟是公平锁还是非公平锁,ReentrantLock 部分源码实现如下:

 public ReentrantLock() {sync = new NonfairSync();}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();
}

从上述源码中可以看出,默认情况下 ReentrantLock 会创建一个非公平锁,如果在创建时显式的设置构造参数的值为 true 时,它就会创建一个公平锁。

2.在 finally 中释放锁

使用 ReentrantLock 时一定要记得释放锁,否则就会导致该锁一直被占用,其他使用该锁的线程则会永久的等待下去,所以我们在使用 ReentrantLock 时,一定要在 finally 中释放锁,这样就可以保证锁一定会被释放。

反例

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {// 加锁操作lock.lock();System.out.println("Hello,ReentrantLock.");// 此处会报异常,导致锁不能正常释放int number = 1 / 0;// 释放锁lock.unlock();System.out.println("锁释放成功!");}
}

以上程序的执行结果如下:

从上述结果可以看出,当出现异常时锁未被正常释放,这样就会导致其他使用该锁的线程永久的处于等待状态。

正例

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {// 加锁操作lock.lock();try {System.out.println("Hello,ReentrantLock.");// 此处会报异常int number = 1 / 0;} finally {// 释放锁lock.unlock();System.out.println("锁释放成功!");}}
}

以上程序的执行结果如下:

从上述结果可以看出,虽然方法中出现了异常情况,但并不影响 ReentrantLock 锁的释放操作,这样其他使用此锁的线程就可以正常获取并运行了。

3.锁不能被释放多次

lock 操作的次数和 unlock 操作的次数必须一一对应,且不能出现一个锁被释放多次的情况,因为这样就会导致程序报错。

反例

一次 lock 对应了两次 unlock 操作,导致程序报错并终止执行,示例代码如下:

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {// 加锁操作lock.lock();// 第一次释放锁try {System.out.println("执行业务 1~");// 业务代码 1......} finally {// 释放锁lock.unlock();System.out.println("锁释锁");}// 第二次释放锁try {System.out.println("执行业务 2~");// 业务代码 2......} finally {// 释放锁lock.unlock();System.out.println("锁释锁");}// 最后的打印操作System.out.println("程序执行完成.");}
}

以上程序的执行结果如下:

从上述结果可以看出,执行第 2 个 unlock 时,程序报错并终止执行了,导致异常之后的代码都未正常执行。

4.lock 不要放在 try 代码内

在使用 ReentrantLock 时,需要注意不要将加锁操作放在 try 代码中,这样会导致未加锁成功就执行了释放锁的操作,从而导致程序执行异常。

反例

import java.util.concurrent.locks.ReentrantLock;public class LockExample {// 创建锁对象private static final ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {try {// 此处异常int num = 1 / 0;// 加锁操作lock.lock();} finally {// 释放锁lock.unlock();System.out.println("锁释锁");}System.out.println("程序执行完成.");}
}

以上程序的执行结果如下:

从上述结果可以看出,如果将加锁操作放在 try 代码中,可能会导致两个问题:

  1. 未加锁成功就执行了释放锁的操作,从而导致了新的异常;

  2. 释放锁的异常会覆盖程序原有的异常,从而增加了排查问题的难度。

总结

本文介绍了 Java 中的显式锁 Lock 及其子类 ReentrantLock 的使用和注意事项,Lock 在 Java 中占据了锁的半壁江山,但在使用时却要注意 4 个问题:

  1. 默认情况下 ReentrantLock 为非公平锁而非公平锁;

  2. 加锁次数和释放锁次数一定要保持一致,否则会导致线程阻塞或程序异常;

  3. 加锁操作一定要放在 try 代码之前,这样可以避免未加锁成功又释放锁的异常;

  4. 释放锁一定要放在 finally 中,否则会导致线程阻塞。

文末福利

今天恰好情人节磊哥联合博文视点出版社,给大家送 3 本何海涛老师的经典书籍《剑指Offer(专项突破版):数据结构与算法名企面试题精讲》,作者从微软起步,面遍各国际大公司,且有近 20 年名企面试官经历,担任面试官面试千余次,积累大量真实试题和现场经验。

中奖规则:评论区留的第 6、16、26 位用户送出此书,免费包邮到家,下周二开奖。

当然,土豪朋友也可以通过下面连接直接购买。

本系列原创文章推荐

1.线程的故事:我的3位母亲成就了优秀的我!

2.线程池的7种创建方式,强烈推荐你用它...

3.轻量级锁一定比重量级锁快吗?

4.这样终止线程,竟然会导致服务宕机?

5.漫画:如何证明sleep不释放锁,而wait释放锁?

6.池化技术到达有多牛?看了这个对比吓我一跳!

7.求求你,别再用wait和notify了!

8.Semaphore自白:限流器用我就对了!

9.CountDownLatch:别浪,等人齐再团!

10.CyclicBarrier:人齐了,老司机就发车了!

11.Java中用户线程和守护线程区别这么大?

12.ThreadLocal不好用?那是你没用对!

13.ThreadLocal内存溢出代码演示和原因分析!

14.SimpleDateFormat线程不安全的5种解决方案!

15.synchronized 加锁 this 和 class 的区别!

16.synchronized 优化手段之锁膨胀机制!

17.synchronized 中的 4 个优化,你知道几个?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544632.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android中如何查看内存(上)

文章参照自&#xff1a;http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-android#2299813像Linux这种现代操作系统的内存使用是很复杂的&#xff0c;因此很难准确的知道你的应用程序使用了好多内存。查看内存使用的方式有很多种…

.NET 4.0 调用 C dll 触发 AccessViolationException 异常的处理方案

一、问题 最近做项目的时候&#xff0c;在调用 c 写的 dll 的时候&#xff0c;遇到一个程序异常&#xff0c;发现捕捉不到&#xff0c;异常为&#xff1a;System.AccessViolationException 二、解决方案 详细内容和原理可以看下面引用的内容&#xff0c;我这里使用的方法是在…

ai逻辑回归_人工智能中的逻辑是什么?

ai逻辑回归人工智能逻辑 (Logic in Artificial Intelligence) Logic, as per the definition of the Oxford dictionary, is "the reasoning conducted or assessed according to strict principles and validity". In Artificial Intelligence also, it carries som…

FFmpeg - 音频解码过程

1. 注册所有解码器 av_register_all(); 2. Codec & CodecContext AVCodec* codec avcodec_find_decoder(CODEC_ID_AAC); if (!codec) { fprintf(stderr, "codec not found\n"); exit(1); } AVCodecContext *codec_ctx avcodec_alloc_con…

php数据类型_PHP数据类型能力问题和解答

php数据类型This section contains Aptitude Questions and Answers on PHP Data Types. 本节包含有关PHP数据类型的 Aptitude问题和解答。 1) There are the following statements that are given below, which of them are correct about data types in PHP? In PHP, varia…

WPF 使用NotifyIcon控件

转载自&#xff1a;https://www.cnblogs.com/celery94/archive/2010/10/26/1861371.html 1.在什么地方找到NotifyIcon 普通的WPF控件基本上都是在该命名空间下&#xff1a;System.Windows.Controls&#xff0c;该命名空间在C:\Program Files\Reference Assemblies\Microsoft\…

SpringBoot 中 4 大核心组件,你了解多少?

Spring Boot 中的 4 大组件分别是&#xff1a;Spring Boot Starter、Spring Boot Autoconfigure、Spring Boot CLI 以及 Spring Boot actuator&#xff0c;接下来&#xff0c;我们分别来看他们的使用和作用。1.Spring Boot Starter1.1 Starter的应用示例<dependency><…

cache命令小结

鉴于健忘症&#xff0c;将常用的cache操作记录下来&#xff0c;便于查询&#xff1a;1、ttcurl -X PUT http://192.168.1.102:14010/dlytest -d "dlytest"curl -X DELETE http://192.168.1.105:15002/2156F298FDD458C321A30B7D26F98E6D 2、memcacheset 命令用于向缓存…

Python operator.truth()函数与示例

operator.truth()函数 (operator.truth() Function) operator.truth() function is a library function of operator module, it is used to check whether the given value is a true/truthy value or not, it returns True if the given value is a true/truthy value, False…

WPF 代码设置NotifyIcon图标

以前做Winform窗口的时候&#xff0c;设置图标非常简便&#xff0c;用WPF还是有区别的。 notifyIcon1.Icon new Icon(System.Windows.Application.GetResourceStream(new Uri("Images/Icon/Moana.ico", UriKind.Relative)).Stream);

双重检查锁,原来是这样演变来的,你了解吗

最近在看Nacos的源代码时&#xff0c;发现多处都使用了“双重检查锁”的机制&#xff0c;算是非常好的实践案例。这篇文章就着案例来分析一下双重检查锁的使用以及优势所在&#xff0c;目的就是让你的代码格调更加高一个层次。同时&#xff0c;基于单例模式&#xff0c;讲解一下…

Linux学习之FTP服务

环境&#xff1a;red hat6.5安装包&#xff1a;vsftp、ftp服务器主配置文件&#xff1a;/etc/vsftpd/vsftpd.conf主配置参数&#xff1a;anonymous_enableYES //&#xff08;默认&#xff09;允许匿名登录anon_upload_enableYES //允许匿名上传文件anon_mkdir_write_enableYES …

WakaTime 记录你的时间(Moana 自动同步信息客户端)

X、写在前面 代码界有一神器&#xff0c;可以记录敲代码的时间&#xff0c;项目名称&#xff0c;编译器等信息&#xff0c;可以极大的满足程序员的虚荣心&#xff0c;它就是 WakaTime 网站链接 WakaTime 可以记录敲代码时间&#xff0c;和具体编辑的文件等信息&#xff0c;并…

图解:为什么非公平锁的性能更高?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 中 synchronized 和 ReentrantLock 默认使用的都是非公平锁&#xff0c;而它们采用非公平锁的原因都是一致的&#…

java timezone_Java TimeZone setDefault()方法与示例

java timezoneTimeZone类的setDefault()方法 (TimeZone Class setDefault() method) setDefault() method is available in java.util package. setDefault()方法在java.util包中可用。 setDefault() method is used to assign the default time zone which is retrieved by us…

5.2 测试计划和估算

5.2 测试计划和估算 2015-06-23 5.2.2. 测试计划活动&#xff08;K3&#xff09; 对整个系统或部分系统可能的测试计划活动包括&#xff1a; 确定测试的范围和风险&#xff0c;明确测试的目标&#xff1b;定义测试的整体方法&#xff08;测试策略&#xff09;&#xff0c;包括测…

Android 模拟器调试的缺点

1.模拟器调试速度太慢&#xff0c;不能清晰真实反映开发中的问题。 2.安卓定制化现象严重&#xff0c;模拟器达不到真机的真实水平&#xff0c;如控件样式、分辨率。 3.模拟器不能模拟所有的API&#xff0c;如Email、电话、短信、横竖屏、GPS、蓝牙、多点触控、震动、服务等基…

java timezone_Java TimeZone getDefault()方法与示例

java timezoneTimeZone类的getDefault()方法 (TimeZone Class getDefault() method) getDefault() method is available in java.util package. getDefault()方法在java.util包中可用。 getDefault() method is used to return the default time zone for this host. getDefaul…

死锁的 4 种排查工具 !

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone死锁&#xff08;Dead Lock&#xff09;指的是两个或两个以上的运算单元&#xff08;进程、线程或协程&#xff09;&#xff0c;都在等待…

搜索百度百科官方创建入口,怎么创建更新公司的百度百科词条呢?

在百度搜索百度百科找到百度百科官方创建入口&#xff0c;可以上传并创建公司类的百度百科词条&#xff0c;创建词条后还可以再修改更新百科词条&#xff0c;最终完善好的百度百科词条将会在百度上获得大量曝光。那么百度百科可以怎么创建&#xff0c;下面洛希爱做百科网把十多…