首页 > 其他分享 >JUC5 多线程锁(下)

JUC5 多线程锁(下)

时间:2022-12-07 18:35:02浏览次数:47  
标签:ReentrantReadWriteLock 读写 StampedLock 读锁 线程 JUC5 多线程 偏向


JUC5 多线程锁(下)_jvm

JUC5 多线程锁(下)_无锁_02

JUC5 多线程锁(下)_读锁_03

JUC5 多线程锁(下)_读写锁_04

JUC5 多线程锁(下)_无锁_05


1. ​​synchronize​​锁升级:无锁,偏向锁,轻量锁,重量锁 (看病:社区医院->三甲医院)

1.1 概述

按照获得锁和释放锁的性能消耗,锁的分类:

1. 无锁状态

2. 偏向锁:不进行​​CAS​​, 测试对象头Mark Word里是否存储当前线程的偏向锁,如果有则获得锁。 竞争出现释放锁。

3. 轻量级锁:CAS

4. 重量级锁

JUC5 多线程锁(下)_读锁_06


JUC5 多线程锁(下)_读写锁_07

synchronized锁: 由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略。

锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位。

JUC5 多线程锁(下)_读锁_08

为什么引入锁升级:尽量减少用户态和内核态的切换,因为他们容易导致阻塞。

JUC5 多线程锁(下)_读锁_09

1.2 无锁:程序不会有锁的竞争

1.3 偏向锁:单线程竞争,指向线程号(java15以后废除,cost过大)

介绍:

主要作用:当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁(偏向锁)
同一个老顾客来访,直接老规矩行方便
偏向锁为了解决只有在一个线程执行同步时提高性能

如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要同步。

偏向锁的撤销:偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行

理论:

JUC5 多线程锁(下)_读锁_10

过程:

JUC5 多线程锁(下)_读锁_11

1.4 轻量锁:多线程竞争,但任意时刻只有一个线程竞争,不存在锁线程竞争太过激烈的情况,也就没有线程阻塞

主要作用:

本质就是自旋锁
有线程来参与锁的竞争,但是获取锁的冲突时间极短

过程:

JUC5 多线程锁(下)_无锁_12

JUC5 多线程锁(下)_读写锁_13

JUC5 多线程锁(下)_读锁_14

1.5 重量锁:会有用户态、内核态切换。指向互斥量,monitor指针

JUC5 多线程锁(下)_读锁_15

1.6 总结

1.6.1 分类

JUC5 多线程锁(下)_无锁_16

JUC5 多线程锁(下)_jvm_17

1.6.2 锁升级流程:先自旋,不行再阻塞

JUC5 多线程锁(下)_读锁_18

2. 无锁,独占锁/排他锁,读写锁,邮戳锁 (ReentrantLock   ReentrantReadWriteLock   StampedLock)

java.util.concurrent.locks

2.0 Lock

JUC5 多线程锁(下)_读锁_19

2.1 演变

1. 排他锁:同一时刻只允许一个线程访问。 如:Mutex, Reentrantlock, synchronized

2.​​ 读写锁​​读写分离。同一时刻允许多个读锁和一个写锁。

JUC5 多线程锁(下)_无锁_20

2.2 独占锁/排他锁

2.3 ReentrantReadWriteLock 可重入读写锁

重点介绍 ReentrantReadWriteLock 可重入读写锁,继承 ReentrantLock,实现ReadWriterLock接口

读写锁定义:一个资源能够被多个读线程访问,或者被一个写线程访问,但不能同时存在读写线程

一句话:只允许读读互存,其他互斥

ReentrantReadWriteLock锁降级:为了保证数据一致性

JUC5 多线程锁(下)_jvm_21

JUC5 多线程锁(下)_无锁_22

JUC5 多线程锁(下)_jvm_23

2.4 StampedLock 版本锁,票据锁:比读写锁更快

它是由锁饥饿问题引出:

ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了 因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写,

如何缓解锁饥饿问题:

使用“公平”策略可以一定程度上缓解这个问题

但是“公平”策略是以牺牲系统吞吐量为代价的

因此,StampedLock类的乐观读锁闪亮登场

JUC5 多线程锁(下)_无锁_24

StampedLock的特点:

  • 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;
  • 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
  • StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)

StampedLock有三种访问模式:

  1. Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
  2. Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
  3. Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式

StampedLock的缺点

  • StampedLock 不支持重入,没有Re开头
  • StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
  • 使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法
  • 如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()

3. Locks锁包下的辅助类

JUC5 多线程锁(下)_无锁_25

3.1 CountDownLatch

JUC5 多线程锁(下)_读写锁_26

3.2 CyclicBarrier

JUC5 多线程锁(下)_读锁_27

JUC5 多线程锁(下)_jvm_28

3.3 Semaphore 

类比:抢车位

JUC5 多线程锁(下)_读锁_29

1. CountDownLatch:允许一个或多个线程等待其他线程完成操作。

2. CyclicBarrier:字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一
组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会
开门,所有被屏障拦截的线程才会继续运行。

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重
置。所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数
器,并让线程重新执行一次。
CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得Cyclic-Barrier
阻塞的线程数量。isBroken()方法用来了解阻塞的线程是否被中断。
 

3. 控制并发线程数的Semaphore: Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

4. 线程间交换数据的Exchanger: Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

标签:ReentrantReadWriteLock,读写,StampedLock,读锁,线程,JUC5,多线程,偏向
From: https://blog.51cto.com/u_15905340/5919910

相关文章

  • JUC4 多线程锁(上)
    1.乐观锁和悲观锁①.悲观锁什么是悲观锁?认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改适合写操作多的场......
  • 多线程--面试题整理
    简述线程,程序、进程的基本概念线程:与进程相似,但线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空......
  • JAVA 实现多线程发售火车票
    publicclassdemo05{publicstaticvoidmain(String[]args){TicketWindowtw=newTicketWindow();newThread(tw,"窗口1").start();ne......
  • 多线程之创建线程
    多线程进程在操作系统中运行的程序就是进程,进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位通常再一个进程中可以包含若干个线程,当然一个进程......
  • Java多线程学习笔记
    程序、进程、线程程序:是为了完成特定任务,用某种语言编写的一组指令的集合,是一段静态的代码。(程序是静态的)进程:是程序的一次动态执行。正在运行的一个程序,进程作为资......
  • JUC高级篇-第2章 多线程锁
    1.乐观锁与悲观锁悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。适合写操作多的场景,先加锁可以保......
  • Python笔记-多进程多线程
    日常运维中,经常需要并发来提升工作效率。Python提供了多线程和多进程两种方式。importtimeimportthreadingimportmultiprocessingdefprint_fun(num):print(time.str......
  • 多线程
    静态代理各种内部类yieldjoin后是继续执行不是重新开始sleep的性质sleep不会释放锁?wait会每个线程都有自己的工作内存内存都是各自的互不影响是拷贝过去的......
  • <二>强弱指针使用场景之 多线程访问共享对象问题
    代码1#include<iostream>#include<thread>usingnamespacestd;classA{public: A(){cout<<"A()"<<endl;} ~A(){cout<<"~A()"<<endl;} vo......
  • 多线程锁等待超时解决方案
    java.util.concurrent.ExecutionException:org.springframework.dao.CannotAcquireLockException:###Errorupdatingdatabase.Cause:com.mysql.cj.jdbc.exception......