首页 > 编程语言 >ReentrantLock源码分析、LockSuppor、ReentrantReadWriteLock、锁优化的方法

ReentrantLock源码分析、LockSuppor、ReentrantReadWriteLock、锁优化的方法

时间:2024-02-05 23:06:44浏览次数:21  
标签:Node AQS ReentrantReadWriteLock lock ReentrantLock 源码 线程 公平

ReentrantLock类图

在这里插入图片描述 在这里插入图片描述 我们看一下重入锁ReentrantLock类关系图,它是实现了Lock接口的类。NonfairSync和FairSync都继承 自抽象类Sync,在ReentrantLock中有非公平锁NonfairSync和公平锁FairSync的实现。 在重入锁ReentrantLock类关系图中,我们可以看到NonfairSync和FairSync都继承自抽象类Sync,而 Sync类继承自抽象类AbstractQueuedSynchronizer(简称AQS)。如果我们看过JUC的源代码,会发现 不仅重入锁用到了AQS, JUC 中绝大部分的同步工具也都是基于AQS构建的。

ReentrantLock源码分析:锁的获取

研究任何框架或工具都就要一个入口,我们以重入锁为切入点来理解AQS的作用及实现。下面我们深入ReentrantLock源码来分析AQS是如何实现线程同步的。

AQS其实使用了一种典型的设计模式:模板方法。我们如果查看AQS的源码可以看到,AQS为一个抽象 类,AQS中大多数方法都是final或private的,也就是说AQS并不希望用户覆盖或直接使用这些方法,而 是只能重写AQS规定的部分方法。

在这里插入图片描述

在这里插入图片描述 我们以重入锁中相对简单的公平锁为例,以获取锁的 lock 方法为入口,一直深入到AQS,来分析多线程 是如何同步获取锁的。 在这里插入图片描述 获取锁时源码的调用过程,时序图如下:

在这里插入图片描述

第一步:ReentrantLock.lock()

ReentrantLock获取锁调用了 lock 方法,我们看下该方法的内部:调用了sync.lock()。

public void lock() {
    sync.lock();
}

sync是Sync类的一个实例,Sync类实际上是ReentrantLock的抽象静态内部类,它集成了AQS来实现重 入锁的具体业务逻辑。AQS是一个同步队列,实现了线程的阻塞和唤醒,没有实现具体的业务功能。在 不同的同步场景中,需要用户继承AQS来实现对应的功能。

我们查看ReentrantLock源码,可以看到,Sync有两个实现类公平锁FairSync和非公平锁NoFairSync。

重入锁实例化时,根据参数fair为属性sync创建对应锁的实例。以公平锁为例,调用sync.lock事实上调用的是FairSync的lock方法。

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

第二步:FairSync.lock()

我们看下该方法的内部,执行了方法acquire(1),acquire为AQS中的final方法,用于竞争锁。

final void lock() {
    acquire(1);
}

第三步:AQS.acquire(1)

线程进入AQS中的acquire方法,arg=1。 这个方法逻辑:先尝试抢占锁,抢占成功,直接返回; 抢占失败,将线程封装成Node节点追加到AQS队列中并使线程阻塞等待。 (1)首先会执行tryAcquire(1)尝试抢占锁,成功返回true,失败返回false。抢占成功了,就不会执行 下面的代码了 (2)抢占锁失败后,执行addWaiter(Node.EXCLUSIVE)将x线程封装成Node节点追加到AQS队列。 (3)然后调用acquireQueued将线程阻塞,线程阻塞。 线程阻塞后,接下来就只需等待其他线程唤醒它,线程被唤醒后会重新竞争锁的使用。 接下来,我们看看这个三个方法具体是如何实现的。

public final void acquire(int arg) {
     if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) 
			selfInterrupt();
}

第四步:FairSync.tryAcquire(1)

尝试获取锁:若获取锁成功,返回true;获取锁失败,返回false。 这个方法逻辑:获取当前的锁状态,如果为无锁状态,当前线程会执行CAS操作尝试获取锁;若当前线 程是重入获取锁,只需增加锁的重入次数即可。

在这里插入图片描述

第五步:AQS.addWaiter(Node.EXCLUSIVE)

线程抢占锁失败后,执行addWaiter(Node.EXCLUSIVE)将线程封装成Node节点追加到AQS队列。 addWaiter(Node mode)的mode表示节点的类型,Node.EXCLUSIVE表示是独占排他锁,也就是说重入 锁是独占锁,用到了AQS的独占模式。 Node定义了两种节点类型:

  • 共享模式:Node.SHARED。共享锁,可以被多个线程同时持有,如读写锁的读锁。
  • 独占模式:Node.EXCLUSIVE。独占很好理解,是自己独占资源,独占排他锁同时只能由一个线程 持有。
staticfinalNodeSHARED=newNode();//共享模式
staticfinalNodeEXCLUSIVE=null;//独占模式

相应的AQS支持两种模式:支持独占模式和共享模式。 在这里插入图片描述

第六步:AQS.acquireQueued(newNode,1)

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

AQS.shouldParkAfterFailedAcquire

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

AQS.parkAndCheckInterrupt

在这里插入图片描述

LockSupport类

在这里插入图片描述

ReentrantLock源码分析:锁的释放

在这里插入图片描述

第一步:ReentrantLock.unlock

在这里插入图片描述

第二步:AQS.release(1)

在这里插入图片描述

第三步:Sync.tryRelease(1)

在这里插入图片描述

第四步:AQS.unparkSuccessor

在这里插入图片描述

第五步:LockSupport.unpark(s.thread)

会唤醒挂起的线程,使被阻塞的线程继续执行。

公平锁和非公平锁源码实现区别

公平锁和非公平锁在获取锁和释放锁时有什么区别呢?

  • 非公平锁与非公平锁释放锁是没有差异,释放锁时调用方法都是AQS的方法。
  • 非公平锁与非公平锁获取锁的差异 我们可以看到上面在公平锁中,线程获得锁的顺序按照请求锁的顺序,按照先来后到的规则获 取锁。如果线程竞争公平锁失败后,都会到AQS同步队列队尾排队,将自己阻塞等待锁的使用 资格,锁被释放后,会从队首开始查找可以获得锁的线程并唤醒。 而非公平锁,允许新线程请求锁时,可以插队,新线程先尝试获取锁,如果获取锁失败,才会 AQS同步队列队尾排队。

我们对比下两种锁的源码,非公平锁与非公平锁获取锁的差异有两处:

  1. lock方法差异: 在这里插入图片描述
  2. tryAcquire差异 FairSync.tryAcquire:公平锁获取锁,若锁为无锁状态时,本着公平原则,新线程在尝试获得锁前,需 先判断AQS同步队列中是否有线程在等待,若有线程在等待,当前线程只能进入同步队列等待。若AQS 同步无线程等待,则通过CAS抢占锁。而非公平锁,不管AQS是否有线程在等待,则都会先通过CAS抢占 锁。 在这里插入图片描述

读写锁ReentrantReadWriteLock

可重入锁ReentrantLock是互斥锁,互斥锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景 下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一 个线程在读时禁止其他线程读势必会导致性能降低,所以就出现了读写锁。 读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的互斥锁有了较 大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会 被阻塞。 读写锁的主要特性:

  • 公平性:支持公平性和非公平性。
  • 重入性:支持重入。读写锁最多支持65535个递归写入锁和65535个递归读取锁。
  • 锁降级:写锁能够降级成为读锁,但读锁不能升级为写锁。遵循获取写锁、获取读锁在释放写锁的 次序

读写锁ReentrantReadWriteLock实现接口ReadWriteLock,该接口维护了一对相关的锁,一个用于只读 操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独 占的。 在这里插入图片描述

ReadWriteLock定义了两个方法。readLock()返回用于读操作的锁,writeLock()返回用于写操作的锁。 ReentrantReadWriteLock定义如下: 在这里插入图片描述 ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠 Sync来实现的。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式 上不一样而已,它的读写锁其实就是两个类:ReadLock、writeLock,这两个类都是lock的实现。 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

锁优化

减少锁持有时间

在这里插入图片描述

减少锁粒度

  • 将大对象拆分成小对象,增加并行度,降低锁竞争。
  • ConcurrentHashMap允许多个线程同 时进入

锁分离

  • 根据功能进行锁分离
  • ReadWriteLock在读多写少时,可以提高性能。

锁消除

  • 锁消除是发生在编译器级别的一种锁优化方式。
  • 有时候我们写的代码完全不需要加锁,却执行了加锁操作。

锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是在某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的请求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。 在这里插入图片描述

标签:Node,AQS,ReentrantReadWriteLock,lock,ReentrantLock,源码,线程,公平
From: https://blog.51cto.com/u_15323027/9612730

相关文章

  • 通达信瞄准底部源码副图
    {股票指标}VAR1:=ma(HHV(HIGH,485),17);VAR2:=MA(HHV(HIGH,222),17);VAR3:=MA(HHV(HIGH,96),17);VAR4:=MA(LLV(LOW,485),17);VAR5:=MA(LLV(LOW,222),17);VAR6:=MA(LLV(LOW,96),17);VAR7:=MA((VAR4*0.96+VAR5*0.96+VAR6*0.96+VAR1*0.558+VAR2*0.558+VAR3*0.558)/6,17);V......
  • 通达信金钱魔鬼源码副图
    {股票指标}VAR2:IF(Ema(CLOSE,5)/EMA(EMA(CLOSE,9),16)<=0.85ANDCLOSE/REF(CLOSE,1)>0.905ANDCLOSE/REF(CLOSE,1)<1.05ANDvol/CAPITAL*100<5,50,0);VAR3:=(-100)*(HHV(HIGH,34)-CLOSE)/(HHV(HIGH,34)-LLV(LOW,34))+100;VAR4:=(-100)*(HHV(HIGH,50)-CLOSE)......
  • 通达信专吸庄血源码副图
    {股票指标}N:=10; VAR1:4*SMA((CLOSE-LLV(LOW,N))/(HHV(HIGH,N)-LLV(LOW,N))*100,5,1)- 3*SMA(SMA((CLOSE-LLV(LOW,N))/(HHV(HIGH,N)-LLV(LOW,N))*100,5,1),3.2,1),COLORYELLOW,LINETHICK1; VAR2:8,COLORGREEN,LINETHICK0;增强体力:IF(CROSS(VAR1,VAR2),80,0),STIC......
  • 通达信私募顶底线源码副图
    {股票指标}底部:0,COLORWHITE;顶部:100,COLORYELLOW;N1:=5;N4:=34;CQ:=100*(C-LLV(L,N4))/(HHV(C,N4)-LLV(L,N4));SAT:=(AMOUNT/C)/(HHV(AMOUNT,N4)/HHV(C,N4));饱和度:=IF(SAT>1,1,SAT)*100;W1:=C=HHV(C,20);W2:=BArslAst(W1);W3:=IF(W2>0,REF(C,W2),REF(C,W2));W4......
  • 通达信精品成交量指标公式源码
    {股票指标}A1:=REF(CLOSE,2);A2:=SMA(maX(CLOSE-A1,0),7,1)/SMA(Abs(CLOSE-A1),7,1)*100;A3:=DATE<=1070615;A4:=REF(LOW,1);A5:=SMA(ABS(LOW-A4),3,1)/SMA(MAX(LOW-A4,0),3,1)*100;A6:=EMA(IF(CLOSE*1.3,A5*10,A5/10),3);A7:=LLV(LOW,29);A8:=HHV(A6,37);A9:=IF......
  • 通达信牛回头主图源码
    {股票指标} 开盘:=(O-REF(C,1))/REF(C,1)*100,NODRAW,COLORLIGREEN;现价:REFDATE(C,DATE),DOTLINE,COLORLIGRAY;半分:(O+C)/2,NODRAW,COLORWHITE;H2:=MAX(H,REF(H,1));L2:=MIN(L,REF(L,1));两日半:=(H2+L2)/2,NODRAW;STICKLINE(ISLASTBARAND两日半,两日半,两日半,......
  • 通达信愚公财神副图源码
    {股票指标} {愚公财神} LC:=REF(CLOSE,1); RSI1:=SMA(MAX(CLOSE-LC,0),3,1)/SMA(ABS(CLOSE-LC),3,1)*100; RSI2:=SMA(MAX(CLOSE-LC,0),5,1)/SMA(ABS(CLOSE-LC),5,1)*100; 买:EMA(RSI1,2),COLORRED,LINETHICK1; 卖:EMA(RSI2,5),LINETHICK1,COLORFFFF00;Q2:=(CL......
  • 通达信龙头选股公式源码副图
    {股票指标}HJ_1:=DYNAINFO(4)>0ANDHHV(HIGH,10)/LLV(LOW,10)<1.25ANDREF(CLOSE,1)<llv(low,15)+(hhv(high,15)-llv(low,15))*0.85and=""close="">OPENANDCLOSE>=HHV(HIGH,10);HJ_2:=IF(DATETODAY(DATE)<999999999,1,DRAWNULL)......
  • 通达信蜘蛛金操盘源码副图
    {股票指标}主力资金:Ema(-100*(HHV(HIGH,34)-CLOSE)/(HHV(HIGH,34)-LLV(LOW,34)),3)+100,COLORRED,LINETHICK2;进入启动区:65;快速拉伸区:80;筹码集中区:50;抄底阶段:20;DRAWTEXT(crOSS(主力资金,抄底阶段),主力资金,'抄底'),COLORFA8282;DRAWTEXT(CROSS(主力资金,筹码集中......
  • 通达信买卖顶底主图源码
    {股票指标}RSV:=Ema((MA(H,2)+MA(L,2))/2,2);原形:=MA(RSV,13),COLOR00AAEE;偏移:=xma(RSV,18),COLOR00FF00;运动趋势:=(原形+偏移)/2,COLORFFFFFF,LINETHICK2;DRAWBAND(运动趋势*1.1,RGB(00,100,100),运动趋势*1.125,RGB(00,100,100));DRAWBAND(运动趋势*1.125,RGB(10......