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读写锁详解

 

 

原文地址:https://www.cnblogs.com/wangpan8721/p/13786409.html