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

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

2019080611330380.jpg


J.U.C脑图


2019022410360263.png


20190224103319901.png

2019022410351861.png

ReentrantLock概述

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对

资源的重复加锁,而不会造成自己阻塞自己。


重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞


ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方

法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。


除此之外,该锁的还支持获取锁时的公平和非公平性选择。实际上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。看使用场景。


公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。


在Java里一共有两类锁, 一类是synchornized同步锁,还有一种是JUC里提供的锁Lock,Lock是个接口,其核心实现类就是ReentrantLock。


ReentrantLock实现 ,主要是采用自旋锁,循环调用CAS操作来实现加锁,避免了使线程进入内核态的阻塞状态


ReentrantLock独有的功能


可指定是公平锁还是非公平锁,所谓公平锁就是先等待的线程先获得锁

提供了一个Condition类,可以分组唤醒需要唤醒的线程

提供能够中断等待锁的线程的机制,lock.lockInterruptibly()


ReentrantLock 常用方法

20190224225519336.png

void  lock()   //加锁 
void  unlock()  //释放锁
boolean isHeldByCurrentThread();   // 当前线程是否保持锁定
boolean isLocked()  // 是否存在任意线程持有锁资源
void lockInterruptbly()  // 如果当前线程未被中断,则获取锁定;如果已中断,则抛出异常(InterruptedException)
int getHoldCount()   // 查询当前线程保持此锁定的个数,即调用lock()方法的次数
int getQueueLength()   // 返回正等待获取此锁定的预估线程数
int getWaitQueueLength(Condition condition)  // 返回与此锁定相关的约定condition的线程预估数
boolean hasQueuedThread(Thread thread)  // 当前线程是否在等待获取锁资源
boolean hasQueuedThreads()  // 是否有线程在等待获取锁资源
boolean hasWaiters(Condition condition)  // 是否存在指定Condition的线程正在等待锁资源
boolean isFair()   // 是否使用的是公平锁


synchronized 和 ReentrantLock的比较


image.png

顺便说下自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。


那该如何选择呢?


如果需要实现ReenTrantLock的三个独有功能时,就选择使用ReenTrantLock, 通常情况下synchronized就能够满足了,而且使用起来简单,由JVM管理,不会产生死锁。


ReentrantLock示例


我们把使用synchronized来确保线程安全的例子,使用ReentrantLock来实现下

20190224225220721.png

多次运行: 线程安全


20190224225245993.png

读写锁ReentrantReadWriteLock


可重入锁ReentrantLock是排他锁,这些锁在同一时刻只允许一个线程进行访问


而读写锁ReentrantReadWriteLock在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。即ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。


在没有任何读写锁的时候才能取得写入的锁,可用于实现悲观读取


读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写,使得并发性相比一般的排他锁有了很大提升。

20190224230243937.png


例子

假设现在有一个类,里面有一个map集合,希望在对其读写的时候能够进行一些线程安全的保护,这时我们就可以使用到ReentrantReadWriteLock

20190224233643425.png

StampedLock


StampedLock是Java8引入的一种新的锁机制,是读写锁的一个改进版本,读写锁虽然分离了读和写的功能,使得读与读之间可以完全并发,但是读和写之间依然是冲突的,读锁会完全阻塞写锁,它使用的依然是悲观的锁策略。如果有大量的读线程,它也有可能引起写线程的饥饿。而StampedLock则提供了一种乐观的读策略,这种乐观策略的锁非常类似于无锁的操作,使得乐观锁完全不会阻塞写线程。


示例

20190224234027251.png

运行结果: 线程安全


20190224234053804.png


Condition


任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、 wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。


Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到 Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。


获取一个Condition必须通过Lock的newCondition()方法。


示例


Condition是一个多线程间协调通信的工具类,使得某个或者某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。


Condition可以非常灵活的操作线程的唤醒,下面是一个线程等待与唤醒的例子,其中用1、2、3、4序号标出了日志输出顺序


20190224234547363.png


输出:

20190224234632296.png


代码


https://github.com/yangshangwei/ConcurrencyMaster

相关文章
|
NoSQL MongoDB Python
python mongodb pymongo 连接 身份认证
python mongodb pymongo 连接 身份认证
367 0
Idea在debug时打上断点没有用 Skipped breakpoint at ... because it happened inside debugger evaluation
Idea在debug时打上断点没有用 Skipped breakpoint at ... because it happened inside debugger evaluation
3427 0
|
安全 Java 测试技术
避免低级错误:深入解析Java的ConcurrentModificationException异常
在软件开发中,我们常常会遇到各种错误和异常。其中有一类比较低级但又常见的错误就是`ConcurrentModificationException`异常。最近了我就写了个这种异常,这个异常通常发生在使用迭代器遍历集合时,同时对集合进行修改,从而导致迭代器检测到集合结构发生变化而抛出异常。在测试环境中可能因为数据量较小或者测试场景不充分未能显现问题,但一旦部署到生产环境,场景增多,并发操作增多,这个低级错误就会爆发。
593 0
避免低级错误:深入解析Java的ConcurrentModificationException异常
|
前端开发 UED 开发者
React 滚动监听 Scroll Listener
本文介绍React中实现滚动监听的方法,涵盖基本概念、常见问题及解决方案。通过监听`window`对象的`scroll`事件,开发者可以在用户滚动时触发自定义逻辑。文章详细探讨了冗余调用、组件卸载时未清理事件监听器、滚动位置不一致等常见问题,并提供了防抖、节流、保存滚动位置等解决方案。同时,强调了跨浏览器兼容性和性能优化的重要性,帮助开发者在实际项目中更好地实现滚动监听功能。
359 17
|
存储 Oracle 关系型数据库
|
JavaScript 前端开发
我为展开收起功能做了动画,被老板称赞!
【8月更文挑战第23天】我为展开收起功能做了动画,被老板称赞!
713 1
我为展开收起功能做了动画,被老板称赞!
|
开发框架 前端开发 JavaScript
ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)
ABP框架中一对多,多对多关系的处理以及功能界面的处理(2)
|
Dart 前端开发 开发者
【Flutter前端技术开发专栏】Flutter Dart语言基础语法解析
【4月更文挑战第30天】Dart是Google为Flutter框架打造的高效编程语言,具有易学性、接口、混入、抽象类等特性。本文概述了Dart的基础语法,包括静态类型(如int、String)、控制流程(条件、循环)、函数、面向对象(类与对象)和异常处理。此外,还介绍了库导入与模块使用,帮助开发者快速入门Flutter开发。通过学习Dart,开发者能创建高性能的应用。
431 0
【Flutter前端技术开发专栏】Flutter Dart语言基础语法解析
|
存储 SQL 数据库
数据仓库原理(二)
数据仓库原理(二)
|
安全 物联网 数据安全/隐私保护
深入理解AMBA总线协议(AXI总结篇)
深入理解AMBA总线协议(AXI总结篇)
2650 1