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


在这里插入图片描述

J.U.C脑图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


ReentrantLock概述

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示该锁可以支持一个线程对
资源的重复加锁,而不会形成本身阻塞本身。
java

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

ReentrantLock虽然没能像synchronized关键字同样支持隐式的重进入,可是在调用lock()方
法时,已经获取到锁的线程,可以再次调用lock()方法获取锁而不被阻塞。github


除此以外,该锁的还支持获取锁时的公平和非公平性选择。实际上,公平的锁机制每每没有非公平的效率高,可是,并非任何场景都是以TPS做为惟一的指标,公平锁可以减小“饥饿”发生的几率,等待越久的请求越是可以获得优先知足。看使用场景。安全

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


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

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

ReentrantLock独有的功能ide

  • 可指定是公平锁仍是非公平锁,所谓公平锁就是先等待的线程先得到锁
  • 提供了一个Condition类,能够分组唤醒须要唤醒的线程
  • 提供可以中断等待锁的线程的机制,lock.lockInterruptibly()

ReentrantLock 经常使用方法

在这里插入图片描述

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的比较
synchornized ReentrantLock
可重入性 可重入(都是同一个线程每进入一次,锁的计数器都自增1,因此要等到锁的计数器降低为0时才能释放锁) 可重入
锁的实现 JVM实现,操做系统级别 JDK实现
性能 在引入偏向锁、轻量级锁(自旋锁)后性能大大提高,官方建议无特殊要求时尽可能使用synchornized,而且新版本的一些jdk源码都由以前的ReentrantLock改为了synchornized 与优化后的synchornized相差不大
功能区别 方便简洁,由编译器负责加锁和释放锁 ,不会产生死锁 需手工操做锁的加锁和释放,忘记释放会产生死锁
锁粒度 粗粒度,不灵活 细粒度,可灵活控制
能否指定公平锁 不能够 能够
能否放弃锁 不能够 能够

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

那该如何选择呢?性能

若是须要实现ReenTrantLock的三个独有功能时,就选择使用ReenTrantLock, 一般状况下synchronized就可以知足了,并且使用起来简单,由JVM管理,不会产生死锁。


ReentrantLock示例

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

在这里插入图片描述

屡次运行: 线程安全
在这里插入图片描述


读写锁ReentrantReadWriteLock

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

而读写锁ReentrantReadWriteLock在同一时刻能够容许多个读线程访问,可是在写线程访问时,全部的读线程和其余写线程均被阻塞。即ReentrantReadWriteLock容许多个读线程同时访问,但不容许写线程和读线程、写线程和写线程同时访问

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

读写锁维护了一对锁,一个读锁和一个写锁,经过分离读锁和写,使得并发性相比通常的排他锁有了很大提高。
在这里插入图片描述


例子

假设如今有一个类,里面有一个map集合,但愿在对其读写的时候可以进行一些线程安全的保护,这时咱们就可使用到ReentrantReadWriteLock

在这里插入图片描述


StampedLock

StampedLock是Java8引入的一种新的锁机制,是读写锁的一个改进版本,读写锁虽然分离了读和写的功能,使得读与读之间能够彻底并发,可是读和写之间依然是冲突的,读锁会彻底阻塞写锁,它使用的依然是悲观的锁策略。若是有大量的读线程,它也有可能引发写线程的饥饿。而StampedLock则提供了一种乐观的读策略,这种乐观策略的锁很是相似于无锁的操做,使得乐观锁彻底不会阻塞写线程。

示例

在这里插入图片描述

运行结果: 线程安全

在这里插入图片描述


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能够很是灵活的操做线程的唤醒,下面是一个线程等待与唤醒的例子,其中用一、二、三、4序号标出了日志输出顺序
在这里插入图片描述

输出:

在这里插入图片描述


代码

https://github.com/yangshangwei/ConcurrencyMaster

相关文章
相关标签/搜索