1.synchronized的功能扩展:重入锁

重入锁可以完全替代synchronized关键字。在JDK5.0的早期版本中,重入锁的性能远远的好于synchronized,但是从JDL6.0开始,JDK在synchronized上做了大量的优化,使两者的性能差距并不大。

重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,我们先看一段简单的重入锁的使用案例:

 /*** @ClassName ReenterLock* @Description 重入锁简单实现方式* @Author JinDuoWang* @Email wangjinduoliuxi@163.com* @Date 9:30 2019/1/18* @Version 1.0**/public class ReenterLock implements Runnable {public static ReentrantLock lock = new ReentrantLock();public static int i = 0;@Overridepublic void run() {for (int j = 0; j < 10000000; j++) {lock.lock();try {i++;} finally {lock.unlock();}}}public static void main(String[] args) throws InterruptedException {ReenterLock reenterLock = new ReenterLock();Thread t1 = new Thread(reenterLock);Thread t2 = new Thread(reenterLock);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}}

这就是一种简单的重入锁实现,重入锁对逻辑控制的灵活性要远远好于synchronized。

在这里肯定有个疑问,为什么叫重入锁呢,这不就是个锁吗?之所以这样叫是因为这种锁是可以反复进入的。当然,这里的反复仅仅局限于一个线程,看个示例:

    // 在这种情况下,一个线程同时获得同一把锁,这是允许的,如果不允许的话同一个线程在第二次获得锁的时候,将会和自己产生死锁。// 但是需要注意的是,如果一次线程获得多个锁,那么你在释放的时候也必须释放相同的次数,如果释放的次数多,会抛出一个java.lang.IllegalMonitorStateException异常,// 反之,如果你加锁两次,但是你只释放了一次,那么后面的线程就永远无法进入临界区。lock.lock();lock.lock();try {i++;} finally {lock.unlock();lock.unlock();}

2.中断响应

对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得了这个锁,要么就保持等待。而是用重入锁的话还会有另外一种可能,那就是线程可以被中断。也就是在等待锁的过程中,可根据需求来取消对锁的请求。

下面的代码产了一个死锁,但得益于锁中断,我们可以很轻松的解决这个死锁。

 /*** @ClassName IntLock* @Description 死锁模拟* @Author JinDuoWang* @Email wangjinduoliuxi@163.com* @Date 9:56 2019/1/18* @Version 1.0**/public class IntLock implements Runnable {public static ReentrantLock reentrantLock1 = new ReentrantLock();public static ReentrantLock reentrantLock2 = new ReentrantLock();int lock;public IntLock(int lock) {this.lock = lock;}@Overridepublic void run() {try {if (lock ==1) {reentrantLock1.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}reentrantLock2.lockInterruptibly();;} else {reentrantLock2.lockInterruptibly();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}reentrantLock1.lockInterruptibly();}} catch (InterruptedException e) {e.printStackTrace();} finally {if (reentrantLock1.isHeldByCurrentThread()) {reentrantLock1.unlock();}if (reentrantLock2.isHeldByCurrentThread()) {reentrantLock2.unlock();}System.out.println(Thread.currentThread().getId() + "线程退出");}}public static void main(String[] args) throws InterruptedException {IntLock r1 = new IntLock(1);IntLock r2 = new IntLock(2);Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start();Thread.sleep(1000);}}输出内容如下:java.lang.InterruptedExceptionat java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)at com.wjd.reenterLock.IntLock.run(IntLock.java:43)at java.lang.Thread.run(Thread.java:748)11线程退出12线程退出

可以看出只有t1真正的完成任务后退出,t2则直接退出,释放资源。

3.锁申请等待限时

除了等待外部通知以外,要避免死锁还有另一重方法,那就是限时等待tryLock(),下面我们来看一下tryLock()简单的实现方式:

    /*** @ClassName TimeLock* @Description 等待现实* @Author JinDuoWang* @Email wangjinduoliuxi@163.com* @Date 10:15 2019/1/18* @Version 1.0**/public class TimeLock implements Runnable {public static ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {try {if (lock.tryLock(5, TimeUnit.SECONDS)) {Thread.sleep(6000);} else {System.out.println("获取锁失败;");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.isHeldByCurrentThread();lock.unlock();}}public static void main(String[] args) {TimeLock timeLock = new TimeLock();Thread t1 = new Thread(timeLock);Thread t2 = new Thread(timeLock);t1.start();t2.start();}}

我们可以看到tryLock()有两个参数,一个代表时长,一个代表计时单位,在这里我们设置了等待5秒,如果超过5秒还没有得到锁,就会返回false。如果成功获得锁就返回true。

在本例中我们让线程休眠了6秒,让tryLock()获取不到锁,我们来看一下输出结果:

获取锁失败;Exception in thread "Thread-1" java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)at com.wjd.reenterLock.TimeLock.run(TimeLock.java:30)at java.lang.Thread.run(Thread.java:748)

有没有感觉到tryLock用起来很爽。

tryLock我们就说到这里。

4.公平锁

在大多数的情况下锁的申请都是非公平的,也就是说线程1请求了锁A,紧接着线程2也请求了锁A,那么当锁A可用的时候,是线程1能抢到锁A还是线程2呢?这是不一定的,系统会随机挑选一个,因此不能保证其公平性。如果我们使用的是synchronized关键字进行锁控制,那么产生的锁就是非公平的。而重入锁允许我们设置公平性,他有一个如下的构造函数:

public ReentrantLock(boolean fair)

当fair等于true的时候,表示锁是公平的。公平锁固然是好,能保证每个线程都能够竞争到锁,但是要实现公平锁,系统必然要维护一个有序队列,因此公平锁的实现成本比较高,性能也非常低下,因此默认的情况下,都会使用非公平的,如果没有特殊的需求,也不需要使用公平锁。公平锁和非公平锁的线程调度也是不一样的,下面我们来看一下公平锁:

    /*** @ClassName FairLock* @Description 公平锁* @Author JinDuoWang* @Email wangjinduoliuxi@163.com* @Date 15:01 2019/1/18* @Version 1.0**/public class FairLock implements Runnable {// 将fair设置为true,则为公平锁public static ReentrantLock reentrantLock = new ReentrantLock(true);@Overridepublic void run() {while (true) {try {reentrantLock.lock();System.out.println(Thread.currentThread().getName() + ":获得了锁");} finally {reentrantLock.unlock();}}}public static void main(String[] args) {FairLock fairLock = new FairLock();Thread t1 = new Thread(fairLock, "THREAD-O1");Thread t2 = new Thread(fairLock, "THREAD-O2");t1.start();t2.start();}}

在代码中,我们把fair设置为true,则指定锁为公平锁。接着t1和t2分别请求这把锁,并且在得到锁之后,进行一个输出,表示自己获得了锁,在公平锁的情况下,得到的输出通常是这样的:

    THREAD-O1:获得了锁THREAD-O2:获得了锁THREAD-O1:获得了锁THREAD-O2:获得了锁THREAD-O1:获得了锁THREAD-O2:获得了锁THREAD-O1:获得了锁THREAD-O2:获得了锁THREAD-O1:获得了锁THREAD-O2:获得了锁

代码会产生大量的输出,这里为了展示我只是截取了一部分拿来进行说明。在这个输出中明显可以看到,两个线程交替获得锁,你一次我一次非常和谐,几乎不会发生一个线程多次获得锁的可能,从而公平锁也得到了保证,我们再来看一下非公平锁,首先我们把代码修改一下,把fair设置为false或者直接不传(默认就是非公平的),修改完以后我们在来看一下输出的结果:

    前面还有很大一片THREAD-01的输出THREAD-O1:获得了锁THREAD-O1:获得了锁THREAD-O1:获得了锁THREAD-O1:获得了锁THREAD-O1:获得了锁THREAD-O1:获得了锁THREAD-O2:获得了锁THREAD-O2:获得了锁THREAD-O2:获得了锁THREAD-O2:获得了锁THREAD-O2:获得了锁后面还有很大一片THREAD-02的输出

可以看到,根据系统的调度,一个线程会倾向于再次获取已持有的锁,这种分配方式是高效的,但是根本毫无无公平性。

对上面的ReentrantLock的几个重要的方法整理如下。

方法名

介绍

lock()

获取锁,如果锁已经被占用,则等待。

lockInterruptibly()

获得锁,但是优先相应中断。

tryLock()

尝试获得锁,如果成功返回true,如果失败返回false。该方法不等待,立即返回。

tryLock(long time, TimeUnit unit)

在给定的时间内,尝试获得锁。

unlock()

释放锁。

就重入锁来看,他主要集中在Java层面。在重入锁的实现中,主要包含三个要素:

第一,是原子状态。原子状态使用CAS操作(会在之后的博文中讲解)来存储当前锁的状态,判断是否已被别的锁持有。

第二,是等待队列。所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统就会从等待的队列中唤醒一个线程,继续工作。

第三,是阻塞原语park()和unpark(),用来挂起和回复线程。没有获得锁的线程将会被挂起。

Ps:有关park()和unpark()的详细介绍会在后面介绍。

重入锁 ReentrantLock相关推荐

  1. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

  2. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  3. java中的账户冻结原理_java可重入锁(ReentrantLock)的实现原理

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

  4. java多线程---重入锁ReentrantLock

    1.定义 重入锁ReentrantLock,支持重入的锁,表示一个线程对资源的重复加锁. 2.底层实现 每个锁关联一个线程持有者和计数器,当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获 ...

  5. 重入锁ReentrantLock详解

    重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.除此之外,该锁的还支持获取锁时的公平和非公平性选择. 在AQS实现中,当一个线程调用Mute ...

  6. Java多线程系列——深入重入锁ReentrantLock

    简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为"独占锁". ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychr ...

  7. java lock可重入_Java源码解析之可重入锁ReentrantLock

    本文基于jdk1.8进行分析. ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock. 首先看一下源码中对ReentrantLock的介绍. ...

  8. Java多线程——重入锁ReentrantLock源码阅读

    上一章<AQS源码阅读>讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲). ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解 ...

  9. Java 并发编程之可重入锁 ReentrantLock

    Java 提供了另外一个可重入锁,ReentrantLock,位于 java.util.concurrent.locks 包下,可以替代 synchronized,并且提供了比 synchronize ...

  10. JDK1.8源码分析:可重入锁ReentrantLock和Condition的实现原理

    synchronized的用法和实现原理 synchronized实现线程同步的用法和实现原理 不足 synchronized在线程同步的使用方面,优点是使用简单,可以自动加锁和解锁,但是也存在一些不 ...

最新文章

  1. python进程和线程_Python进程与线程知识
  2. 我这些年的项目管理心得...
  3. vsftp实现只能上传不能下载、删除权限配置
  4. 接入腾讯云短信服务(史上最详细+该短信服务如何申请成功+发送短信验证码API讲解+相关错误分析)
  5. 使用ADO操作数据库时一个好用的VARIANT类!
  6. xcopy复制文件夹及其子文件_嗨学习:如何给电脑中文件夹设置密码
  7. POJ 2226 二分图最小覆盖
  8. 《大数据、小数据、无数据:网络世界的数据学术》一 3.5 交流融合
  9. Redis的AOF持久化的实现
  10. 利用sobel算子提取图像的水平特征和竖直特征
  11. Matlab信号处理
  12. 自己动手写一个分库分表中间件(六)分布式事务问题解决思路<二>动态事务管理器
  13. 自监督论文阅读笔记 Urban feature analysis from aerial remote sensing imagery using self-supervised and semi-s
  14. 输出魔方阵,所谓魔方阵是指这样的方阵,它的每一行,每一列和对角线之和均相等。例如,三阶魔方阵为
  15. 对淘宝双飞翼布局的的一点理解
  16. 《Adobe Illustrator CS5中文版经典教程》—第0课0.15节创建和编辑渐变
  17. mysql compact_MySQL基本操作
  18. 如何用英文说明一种方法的优缺点(优点和缺点)
  19. 算法之美 | 位运算的巧妙奥秘(一) | JAVA中位运算的深入浅出
  20. 社会趣谈之无良媒体篇:北京一男子与充气娃娃结婚

热门文章

  1. 在PHP中rand更新图片,制作刷新可以改变的图片。
  2. C语言进阶入门(一)
  3. Camtasia Studio 2023怎么导出mp4格式的视频的详细教程介绍
  4. react-ueditor-xiumi,秀米编辑器首次加载图片上传,视频上传无法使用问题
  5. 青龙脚本(微信阅读带脚本)(已废)
  6. 【NIFI】1.11插入/迁移数据爬坑问题记录
  7. 动物园的铲屎官Zookeeper——原理篇
  8. 读书随记——《傲慢与偏见》(2)
  9. 什么是懒加载,如何实现图片或列表懒加载?
  10. 使用webview实现刮刮卡效果,模拟器有刮卡效果,真机无效果