zoukankan      html  css  js  c++  java
  • ReentrantLock AQS ReentrantReadWriteLock

    0,Sychronized与ReentrantLock区别(两种都是常见可重入排他锁)

    ①ReentrantLock是JDK实现的,Sychronized是JVM实现,通过底层指令控制。

    ②Reentrant支持非公平锁和公平锁,sychronized只支持非公平锁。

    ③ReentrantLock可绑定多个条件(Condition)

    对象锁靠Object类中,wait(),notify(),notifyAll(),通过对象调用(只有一个)

    Contition中对象可以依赖lock定义多个,通过condition对象调用await(),signal(),signalAll().

    ④Reentrantlock支持可中断,sychronizd阻塞等待队列中的线程被中断唤醒会抛出异常,reentrantlock唤醒

    后根据情况执行其他操作,具体搞什么我也没搞清楚,看这篇博客,很全面!!!一行一行代码分析AQS一,二,三

    好像意思就是,中断也是Thread类中一个boolean标识,有点像state标识的意思,并不是操作系统里的哪个中断!

    1,ReentrantLock 可重入锁(和synchronized一样是,排他锁/独占锁)

    是一个类,实现Lock接口,内部类Sync(同步器)继承自AbstractQueuedSynchronizer(AQS),队列同步器。

    1.1用法:生声明一个锁对象,调用lock(),unlock()即可对代码块加锁。

    public class NoFairLockTest {
    
    
        public static void main(String[] args) {
    
            
            ReentrantLock lock = new ReentrantLock();
    
            try {
    
                //加锁
                lock.lock();
    
                //模拟业务处理用时
                TimeUnit.SECONDS.sleep(1);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
    
            } finally {
                //释放锁
                lock.unlock();
            }
    
        }
    }

    2,公平锁与非公平锁

    ReentrantLock支持公平锁和非公平锁

      • 公平锁:等待队列(FIFO),等待最久的先获得锁,获取锁是顺序,每次同步队列首节点线程先获取锁
      • 非公平锁:当线程调用lock()后,先CAS抢一次锁成功直接获取锁(忽略等待队列),比公平锁,有更多抢锁机会。
      • 公平锁往往没有非公平锁效率高吗,有一部分线程结束释放锁后立马获取锁,刚释放的线程获取锁的纪律几率更大,

    这种情况非公平锁可以减少,线程上下文切换,吞吐量更大。

    Reentrantlock默认为非公平锁,看构造函数:,无参构造函数默认创建非公平同步器,有参构造函数根据boolean参数创建

        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

    3,Sync,同步器,

    同步器可以理解为抢锁机制(排队机制),ReentrantLock的同步器(内部类),继承与AQS。

     

    4,AQS,

    全称AbstractQueuedSynchronizer,队列同步器

    4.1,源码分析:一行一行源码分析清楚AbstractQueuedSynchronizer

             从源码角度理解ReentrantLock

    AQS结构:①state(同步状态,同步标识)

    ②同步队列(FIFO),(Node节点关系表示的,虚拟双向队列)头节点和尾节点

    ③排队机制(同步机制)

    ④等待/通知机制   Condition

    • state同步状态

    • 内部类:Node节点类(节点关系组成同步队列):

            • Waitstate等待状态       -1为标准等待状态(-2,-3暂时不管),1为取消等待(在等待队列中挂起阻塞自旋获取同步状态的线程被

    中断唤醒或者其他原因,取消等待)。 即>0就表示他不等了----他不应该在阻塞队列中

     

    未指定状态的Node构造函数没有指定Waitstate值,默认未int类型默认值0,

        • 同步队列示意图:头节点为持有当前锁的线程,按功能上说,不算在同步对列内。

     

    • 排队(同步)机制:---独占锁(由ReentrantLock引出AQS,先看独占锁)

    独占式同步状态获取:

                

    ①acquire()获取同步状态方法。首先CAS尝试try一下state是不是空闲,CASState(0,1),如果是直接获取同步状态。如果不是则addWaiter()

           将节点添加至同步队列。

    ②addWaiter()函数构造节点,若队尾不空,将节点添加至队尾,若空则,调用enq()方法

    enq()方法通过死循环CAS(自旋)来保证多线程下节点正确添加,并返回节点

     ③添加完节点进入acquire()方法中的acquireQueued(②返回的节点--新增节点)方法

                   <1>该方法让节点进入同步队列之后()一直处于自旋尝试获取同步状态,在每一次自旋尝试中,if (p == head && tryAcquire(arg))

    只有前驱节点是头节点(当前获得同步状态的节点)

                   即自旋节点为下一个要唤醒的节点。才去获取同步状态,若获取成功,设置为head(head不属于同步队列),false=true(即获取到同步状态)

    finally中执行取消等待cancelAcquire(node)。

     

     <2>若失败,进行shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()检查。

    shouldpark,是否挂起,加入等待队列的节点是需要前一个节点唤醒的,而需要确保前一个节点是能唤醒当前节点,即

    前一个节点的等待状态<0,>0则说明已经取消等待了,要往前找一个靠谱的。后面else里是新建节点默认0在这里将tail设置未-1;

     

    parkAndCheckInterrupt()检查中断情况。,则挂起线程,返回中断情况,

                      

     shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()都是在自旋循环执行的。acquire()获取执行完了,线程要么获取同步状态,

    要么进入同步队列自旋抢同步状态。

    独占式同步状态释放:

    unparkSucessor()用于唤醒下一个节点。

    共享式获取同步状态:(独占式用于实现排他锁--ReentrantLock,共享式用于实现读写锁ReentrantReadWriteLock) 

    写操作对资源独占式访问,渎操作对资源共享式访问。

    ①acquireShared() ->doAcquireShared()

    与独占式一样,先将节点加入同步队列,在自旋抢同步状态,除获取条件不同是,前驱节点为head且tryAcquireShared()

    返回>=0;  细节将在后面ReentrantReadWriteLock中讨论,tryAcquireShared()实现在那里

     

     

    • 内部类  ConditionObject实现Condition接口,Condition主要方法:await(),sifnal(),signalAll(),

    有两个Node对象成员,firstWaiter,lastWaitr,前面说过Node节点中有一个nextWaiter成员,即每一个ConditionObject维护了一个单链表--条件队列

     继承接口要实现主要方法,

    ①await()

    ②signal()   doSignal()  transferForSignal()

           调用signal()的必须是获取同步状态的当前线程

    找到一个没有取消排队的WaiterNode,转移

    将节点从条件队列移至等待队列,enq后面的方法为中断处理,详情看这里

     

    综上AQS结构为:

    AQS封装了等待队列和条件队列的维护过程,自定义同步器,只需要实现根据state的争抢机制即可

     

     

    5,了解完AQS,回到Reentrant Lock

    ①参数:

    自定义同步器sync,

    ②构造方法:

    默认是非公平锁(非公平同步器),可以选择公平锁(公平同步器)

    ③内部类Sync继承AQS

    两个子类NonfairSync非公平同步器,FairSync公平同步器

    • NonfairSync类

    acquire(),AQS中的独占是获取同步状态,

    nonfairTryAcquire()  在Sync类中:

    独占式重入锁:state为0,表示锁空,不为0(>0),先比较持有锁线程,相同--重入,不相同--进入AQS独占式获取同步状态流程。

    非公平:线程调用lock方法,先执行CAS抢一次,失败再进队列

    • fairSync类:

     

    非公平锁相对于公平锁,对新晋抢锁线程更友好,新来的比等的最久的优先级高,有一部分线程结束释放锁后立马获取锁,刚释放的线程获取锁的纪律几率更大,

     这种情况非公平锁可以减少,线程上下文切换,吞吐量更大。

    相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

     

    ④特殊方法:

    Condition接口被AQS中ConditionObject内部类实现,引用在通过实现自定义同步器的锁(如ReentrantLocl)中,

    sync.newCondition()在ReentrantLocl内部类自定义同步器Sync中

     

    可以理解为,Condition对象(虽然是接口,但是中间封装了ConditionObject这一步)是通过AQS实现自定义同步器的某些锁类(如ReentrantLock)

    对象调用newCondition()创建的。看起来是封装在Lock类中。实则背后为AQS服务,

     

    6,面试题:Sychronized与ReentrantLock区别(两种都是常见可重入排他锁)

    ①ReentrantLock是JDK实现的,Sychronized是JVM实现,通过底层指令控制。

    ②Reentrant支持非公平锁和公平锁,sychronized只支持非公平锁。

    ③ReentrantLock可绑定多个条件(Condition)

    对象锁靠Object类中,wait(),notify(),notifyAll(),通过对象调用(只有一个)

    Contition中对象可以依赖lock定义多个,通过condition对象调用await(),signal(),signalAll().

    ④Reentrantlock支持可中断,sychronizd阻塞等待队列中的线程被中断唤醒会抛出异常,reentrantlock唤醒

    后根据情况执行其他操作,具体搞什么我也没搞清楚,看这篇博客,很全面!!!一行一行代码分析AQS一,二,三

    好像意思就是,中断也是Thread类中一个boolean标识,有点像state标识的意思,并不是操作系统里的哪个中断!

     

     

     7,都到这里了,顺势看一下ReentrantReadWriteLock,看这篇就行

    也支持公平非公平锁 ,细节在ReentrantLocK中体现了,这里不赘述了

    ①参数:

    • 自定义同步器sync(内部类对象继承AQS)
    • 读锁(内部类对象)
    • 写锁(内部类对象)

    ②内部类:

    • Sync自定义同步器:

     AQS中只有一个同步状态(int),读锁和写锁都要用,就拆开用,int32位,高16位读锁用,低16位写锁用,

    独占式抢锁:--给写锁用的

    尝试获取写锁,跟上面很像

    独占式释放:

     共享式获取:---用于读锁

    • protected final int tryAcquireShared(),这个函数返回的是int,独占式返回的是成不成功,在AQS的共享式获取那里

    说过,执行AQS中共享式获取,要tryAcquireShared()返回>=0;说明共享式抢锁有>2种状态,不能用boolean区分。

    • tryAcquireShared()

     

     readerShouldBlock()通过,Sync子类NonfairSync和fairSync调用,AQS中apparentlyFirstQueuedIsExclusive()

    该方法成立的条件是,head和等待head的节点都非空,且等待节点是写操作,即当一个写操作下一个就抢时,读操作应该

    让一下,去fullTryAcquireShared();以免再读很多,写很少的情况下,写一直抢不到,因为写进入时,要等读操作都退出。

    • fullTryAcquireShared()

     后半部分看这里吧,说的很清楚

    •  tryReadLock()

    共享式释放

    • tryReleaseShared

     

    • 写锁类,写操作是独占的,就等于一个ReentrantLock,自定义同步器调用ReentrantReadWriteLock的自定义同步器sync的独占式同步状态的获取与释放。

    • 读锁类,  也等于一个独立的共享锁,自定义同步器调用ReentrantReadWriteLock的自定义同步器sync的共享式同步状态的获取与释放

    ④防止写锁饥饿:

    上面介绍过了,等待队列下一个节点是写,新晋读应该让。

    ⑤锁降级:

    已获取写锁的线程可以获取读锁,把自己的读锁放进来,防止自己写锁修改了数据还没来得及用就被别的线程修改了,造成数据不可见。

    不支持锁升级,即已获取读锁线程不能再获取写锁,只要有读锁,写锁就不能抢。

     

    总结:这篇也很好copy一下流程图:

    在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。

    在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。

    写获取流程:

    写锁释放:

    读锁获取:

    读锁释放;

     

     

     

    参考:

    AQS源码分析

    ReentranReadWriteLock源码

    ReentrantReadWriteLock读写锁详解

     

     

  • 相关阅读:
    hdu5728 PowMod
    CF1156E Special Segments of Permutation
    CF1182E Product Oriented Recurrence
    CF1082E Increasing Frequency
    CF623B Array GCD
    CF1168B Good Triple
    CF1175E Minimal Segment Cover
    php 正则
    windows 下安装composer
    windows apache "The requested operation has failed" 启动失败
  • 原文地址:https://www.cnblogs.com/wangpan8721/p/13786409.html
Copyright © 2011-2022 走看看