大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比)

看本文章前,希望先看以下相关文章:




Lock接口

  • 前面的文章已经对LOCK接口做了相关介绍,再简单回顾:
  • 同步锁 synchronized :隐式锁
  • Lock接口实现的锁:显示锁,即锁的持有和释放都由我们手动编写。jdk1.5中引入了此接口
  • Lock接口由实现类来实现(本文用的ReentrantLock类实现),用法:
     Lock lock = new ReentrantLock();
     try{
         lock.lock();
         
         //临界区
     }finally {
         lock.unlock();
     }
  • Lock接口的所有方法:
public interface Lock {
	//加锁
    void lock();
	//获取锁可响应中断(相比较synchronized的优点):
    void lockInterruptibly() throws InterruptedException;
	//尝试非阻塞获取锁,调用该方法立即返回结果,而不是一直等待(阻塞)
    boolean tryLock();
	//尝试非阻塞获取锁,在规定时间如果没有获取锁,就返回false,相反为true
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	//解锁
    void unlock();
    //获取等待通知组件,该组件与当前的锁绑定,当前线程只有获得了锁才能调用该组件的await()方法、调用后释放锁
    Condition newCondition();
}
  • 通过Lock接口的方法我们可以得知(LOCK相比于synchronized的优点):
    • 响应中断lockInterruptibly() synchronized在等待获取锁时是不可中的);
    • 超时中断获取锁的获取:tryLock() synchronized会一直等待锁;
    • 等待通知组件Condition newCondition() 相比synchronized更加灵活
  • 进一步分析Lock接口的实现类之一ReentrantLock

重入锁ReentrantLock

  • 前面的文章对ReentrantLock有了介绍,本文章重点介绍底层实现,这里只是简单回顾:
  • 重入锁ReetrantLock,JDK 1.5新增的类(因为LOCK接口是JDK1.5引入的,相应的实现类同时引入),作用与synchronized关键字相当,但比synchronized更加灵活(虽然在JDK1.6之后,synchronized提供了便捷性的隐式获取锁释放锁机制(基于JVM机制),但是 它却缺少了获取锁与释放锁的可操作性,可中断、超时获取锁 ,且它为独占式在高并发场景下性能大打折扣)。
  • ReentrantLock本身也是一种支持重进入的锁:即该锁可以支持一个线程对共享资源重复加锁;
  • ReentrantLock同时也支持公平锁与非公平锁(因为这两个锁是继承重入锁ReentrantLock);
    • 公平锁:按照先后顺序,排队靠前的先得到锁,那么这就是公平锁;
    • 非公平锁:对于锁的获取并没有时间上的先后顺序,不排队吧;
    • 比较:非公平锁机制的效率往往会胜过公平锁的机制,但在某些场景下,可能更注重时间先后顺序,那么公平锁自然是很好的选择(适合最重要🧡🧡🧡🧡(好比女朋友))。
  • 相关方法:
	//查询当前线程保持此锁的次数。
    public int getHoldCount() {
        return sync.getHoldCount();
    }
	//返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。      
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }
	//查询此锁是否由被线程保持。 
    public boolean isLocked() {
        return sync.isLocked();
    }
	//查询是否是公平锁
    public final boolean isFair() {
        return sync instanceof FairSync;
    }
	//返回目前拥有此锁的线程(独占锁,共享锁),如果此锁不被任何线程拥有,则返回 null。    
    protected Thread getOwner() {
        return sync.getOwner();
    }
	// 查询给定线程是否正在等待获取此锁。  
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
	// 查询给定线程是否正在等待获取此锁。  
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }
    //查询同步队列的长度估计数(后文讲)
    public final int getQueueLength() {
        return sync.getQueueLength();
    }
	//查询是否有些线程正在等待与此锁有关的conditon条件。     
    public boolean hasWaiters(Condition condition) {
    }
    //返回等待与此锁相关的给定条件的线程估计数。 
    public int getWaitQueueLength(Condition condition) {
    }
	// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
    protected Collection<Thread> getWaitingThreads(Condition condition) {
    }
}
  • 开始进入本篇文章的重点章节:💕💕💕💕💕
    • 实际上ReetrantLock以及基于Lock接口下的其他锁都是基于AQS并发框架实现的;
    • 我们先深入了解AQS然后再去对应ReetrantLock的内部实现原理

并发基础组件AQS与ReetrantLock (JDK15)

  • AQS的工作原理:
    • AbstractQueuedSynchronizer 称为 队列同步器 (简称AQS);
    • 它是用来构建锁或其他同步组件的 基础框架
    • state变量: 内部通过一个int类型的成员变量state来控制同步状态private volatile int state;:💘💘💘
      • 当state=0时,没有任何线程占有共享资源的锁,
      • 当state>=1时,有线程正占用锁,其他线程必须加入同步队列进行等待;
    • 同步队列:💘💘💘💘💘
      • AQS内部通过内部类Node构成FIFO的同步队列 abstract static class Node { ...}来完成线程获取锁的排队工作;
        dsadasdssaddsa
    • 等待队列: 💘💘💘💘💘
      • 利用内部类ConditionObject构建等待队列
      • 当Condition调用a wait()方法后,线程将会加入等待队列中,
      • 当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行同步状态state(也就是锁)竞争
    • 两种队列的使用:
      • 当线程请求锁而等待,将加入同步队列等待
      • 通过Condition调用await()方法释放锁后,将加入等待队列(可有多个)
  • AQS的内部类:Exclusive Node、Node、SharedNode、ConditionObject、ConditionNode
  • AQS 定义了两种资源共享方式:
    • Exclusive: 独占,只有一个线程能执行,如ReentrantLock
    • Share: 共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier
    • 不同的自定义的同步器争用共享资源的方式也不同。

AQS的同步队列模型

																	抽象AQS类🧡
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
	
    private static final long serialVersionUID = 7373984972572414691L;  序列化版本号🧡
    
    protected AbstractQueuedSynchronizer() { }						构造方法🧡
   
    // Node status bits, also used as argument and return values
    static final int WAITING   = 1;          // 常量1,表示线程等待
    static final int CANCELLED = 0x80000000; // 常量负数,表示取消等待
    static final int COND      = 2;          // 和等待队列有关
    
     *  +------+  prev +-------+       +------+   		通过Node实现了同步队列🧡
     *  | head | <---- | first | <---- | tail |
     *  +------+       +-------+       +------+
     * 
    abstract static class Node {
        volatile Node prev;       									链表中的prev,volatile修饰🧡
        volatile Node next;       									链表中的next,volatile修饰🧡
        Thread waiter;                                              Node包装的线程🧡
        volatile int status;     									原子位操作,相当于状态,volatile修饰🧡
    }

    private transient volatile Node head;						    同步队列的头结点,volatile修饰🧡

    private transient volatile Node tail;							同步队列的尾结点,volatile修饰🧡

    private volatile int state;                                     同步状态,state==1,放入同步队列中,volatile修饰🧡
	
	//其他代码...

在这里插入图片描述
同步队列的模型 (CLH队列):

在这里插入图片描述

  • Node类的源码和模型分析:
    • head和tail分别是AQS中的变量,属于Node类;
    • head:指向同步队列的头部,注意head为 空结点 ,不存储信息;
    • tail:指向同步队列的队尾,存储信息;
    • 双向链表:同步队列采用的是双向链表的结构:结点增删操作的复杂度低
    • state变量: 代表同步状态,上面已提到,不再赘述;
    • Node结点:
      • 对每一个访问临界区(共同的代码)的线程的封装,从图中的Node的数据结构可看出;
      • 其中包含了需要同步的线程本身线程的状态status前继结点prev后继结点next;

不同JDK的Node节点源码分析:

  • JDK8的Node源码 (不是抽象类了):
static final class Node {								JDK9之前的吧,稍后看一下JDK15💛

    static final Node SHARED = new Node();              共享模式💛
   
    static final Node EXCLUSIVE = null;					独占模式💛

    static final int CANCELLED =  1;					标识线程已处于结束状态💛

 // 值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,
 //即结束状态,进入该状态后的结点将不会再变化。
   
    static final int SIGNAL    = -1;					标识线程已处于可被唤醒状态💛

//值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点(头节点)的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。
//说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。  
   
    static final int CONDITION = -2;					标识线程已处于条件状态💛
    
//值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,
//当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

    static final int PROPAGATE = -3;					与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态💛
	//0状态:值为0,代表初始化状态

    volatile int waitStatus;							同步队列中结点状态:CANCELLED、SIGNAL、CONDITION、PROPAGATE 4种💛
    
    volatile Node prev;									同步队列中前驱结点💛
    
    volatile Node next;									同步队列中后继结点💛

    volatile Thread thread;								请求锁的线程💛

    Node nextWaiter;									等待队列中的后继结点,这个与Condition的等待队列有关💛

    final boolean isShared() {                          判断是否为共享模式💛
        return nextWaiter == SHARED;
    }
    final Node predecessor() throws NullPointerException {   获取前驱结点💛
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
    //省略其他代码
}
  • JDK15的Node源码:
													Node中的status状态🧡
    static final int WAITING   = 1;          		常量1,表示线程等待🧡
    static final int CANCELLED = 0x80000000;        常量负数,表示取消等待,从同步队列中移除🧡
    static final int COND      = 2;          		在等待队列中,下面具体分析🧡

    /** CLH Nodes */
    abstract static class Node {
        volatile Node prev;      					同步队列中前驱结点🧡
        volatile Node next;        					同步队列中后继结点🧡
        Thread waiter;           					请求锁的线程🧡
        volatile int status;     				    同步队列中结点状态:WAITING、COND、CANCELLED 3种🧡

        								           					 以下方法底层都是CAS操作🧡
        final boolean casPrev(Node c, Node v) {  // for cleanQueue
            return U.weakCompareAndSetReference(this, PREV, c, v);
        }
        final boolean casNext(Node c, Node v) {  // for cleanQueue
            return U.weakCompareAndSetReference(this, NEXT, c, v);
        }
        final int getAndUnsetStatus(int v) {     // for signalling
            return U.getAndBitwiseAndInt(this, STATUS, ~v);
        }
        final void setPrevRelaxed(Node p) {      // for off-queue assignment
            U.putReference(this, PREV, p);
        }
        final void setStatusRelaxed(int s) {     // for off-queue assignment
            U.putInt(this, STATUS, s);
        }
        final void clearStatus() {               // for reducing unneeded signals
            U.putIntOpaque(this, STATUS, 0);
        }

        private static final long STATUS
            = U.objectFieldOffset(Node.class, "status");
        private static final long NEXT
            = U.objectFieldOffset(Node.class, "next");
        private static final long PREV
            = U.objectFieldOffset(Node.class, "prev");
    }

  												      相比于早期的JDK,JDK15将Node的静态类有好几种🧡
    static final class ExclusiveNode extends Node { } 继承Node类的独占模式下的Node类🧡
    
    static final class SharedNode extends Node { }	  继承Node类的共享模式下的Node类🧡	  
    
    static final class ConditionNode extends Node implements ForkJoinPool.ManagedBlocker {
        ConditionNode nextWaiter;           与等待队列中的后继结点,这个与Condition的等待队列有关🧡
    }
  • AQS中的Node类相关源码分析:
    • ExclusiveNode、SharedNode 代表的其实就是独占模式、和共享模式下的Node类;
    • 独占模式: 同一个时间段只能有一个线程对共享资源进行操作,其他的请求共享资源的线程需要排队等待,如:ReentrantLock
    • 共享模式: 一个锁允许多线程同时操作,比如信号量Sempaphore采用的就是基于AQS的共享模式;
    • 变量status则表示当前被封装成Node结点在同步队列中的的等待状态:
      • 共有3种取值CANCELLED 、WAITING、COND:
      • WAITING = 1: 被标识为等待唤醒状态的后继结点,当该节点释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为WAITING 状态的后继结点的线程执行。
      • CANCELLED = 0x80000000(负数): 在同步队列中等待的线程等待超时或被中断 ,需要从同步队列中取消该Node的结点,其结点的status为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
      • COND = 2:Condition相关,该标识的结点处于等待队列,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列----->同步队列中,等待获取同步锁。
      • 其实还有为0的情况,当作初始化状态吧,这样想应该没问题。😋😋😋😋
    • ConditionNode及内部的nextWaiter: 与Condition相关,代表等待队列中的后继结点 ,后续讲解。🟢🟢🟢

  • AQS的总结: AQS作为基础组件,对于锁的实现存在两种不同的模式:🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡🧡
    • 共享模式 (如Semaphore);

    • 独占模式 (如ReetrantLock);

    • 无论是共享模式还是独占模式的实现类,其内部都是基于AQS实现的,也都维持着一个虚拟的同步队列,当没有请求到锁时,需将其加入同步队列等待获取锁,而这系列操作都由AQS协助完成,这也是作为基础组件的原因

    • 无论是Semaphore还是ReetrantLock,其内部绝大多数方法都是间接调用AQS完成的。

    • 我们通过结构图进一步看AQS的构造: 在这里插入图片描述

    • 通过图我们知道AQS下有几种:

      • CountDownLatch、ThreadPoolExecutor(后边文章介绍);
      • ReentrantLock(独占锁、本文章介绍)
      • ReentrantReadWriteLock(读共享、写独占后边文章介绍)
      • Semaphore(共享锁、后边文章介绍):
      • 注意:ReentrantLock、ReentrantReadWriteLock、Semaphore都有对应的公平锁、非公平锁
    • ReentrantLock为例,简单讲解ReentrantLock(独占锁)与AQS的关系 : 在这里插入图片描述

      • AbstractOwnableSynchronizer: 抽象类,定义了存储独占当前锁的线程和获取的方法(注意只有独占锁
      • AbstractQueuedSynchronizer: 抽象类,AQS框架核心类
        • 其内部以 虚拟队列的方式 管理线程的锁的获取与锁的释放;
        • 其中获取锁(tryAcquire方法)和释放锁(tryRelease方法)并没有提供默认实现,需要子类(一般通过各种锁的继承AQS的内部类Sync来写) 重写这两个方法实现具体逻辑 ,目的是使开发人员可以自由定义获取锁以及释放锁的方式
      • Sync: 抽象类,在此是ReentrantLock的内部类,继承自AQS,实现了释放锁的操作(tryRelease()方法),并提供了 abstract boolean initialTryLock() 抽象方法(这个方法被Sync类的lock()方法调用),由其子类来具体实现。
      • Node: AQS的内部类,用于构建虚拟队列(链表双向链表),管理需要获取锁的线程。
      • NonfairSync: 是ReentrantLock(读写锁,信号量都有各自的…)的内部类,继承自Sync,非公平锁的实现类。
      • FairSync: 是ReentrantLock(读写锁,信号量都有各自的…)的内部类,继承自Sync,公平锁的实现类。
      • ReentrantLock: 实现了Lock接口的,其内部类有Sync、NonfairSync、FairSync,在创建时可以根据fair参数决定创建NonfairSync(默认非公平锁)还是FairSync。
    • ReentrantLock内部存在3个实现类:

      • Sync:继承自AQS实现了解锁tryRelease()方法
      • NonfairSync:继承自Sync,实现了获取锁的tryAcquire()方法
      • FairSync: 继承自Sync,实现了获取锁的tryAcquire()方法
      • ReentrantLock的所有方法调用都通过间接调用AQS和Sync类及其子类来完成的
    • AQS的深入理解:(总结自别人的文章,大佬!!!)

      • 从上述类图可以看出AQS是一个抽象类,但请注意其源码中并没一个抽象的方法,为什么呢
        • 这是因为AQS只是作为一个基础组件,并不希望直接作为直接操作类对外输出,而更倾向于作为基础组件,为真正的实现类提供基础设施,如构建同步队列控制同步状态等;
      • 事实上,从设计模式角度来看,AQS是采用的模板模式的方式构建的,其内部除了提供并发操作核心方法(我感觉是CAS)以及同步队列操作外,还提供了一些模板方法让子类自己实现,如加锁操作以及解锁操作,为什么这么做?
        • 这是因为AQS作为基础组件,封装的是核心并发操作,但是实现上分为两种模式,即共享模式与独占模式
        • 这两种模式的加锁与解锁实现方式是不一样的,但AQS 只关注内部公共方法实现并不关心外部不同模式的实现,所以提供了模板方法给子类使用
        • 如实现独占锁,如ReentrantLock需要自己实现tryAcquire()方法、tryRelease() 方法;
        • 如实现共享模式的Semaphore,则需要实现tryAcquireShared()方法、tryReleaseShared() 方法;
      • AQS给出模板,子类实现,这样做的好处是显而易见的?
        • 无论是共享模式还是独占模式,其基础的实现都是同一套组件(AQS),只不过是加锁解锁的逻辑不同罢了,更重要的是如果我们需要自定义锁的话,也变得非常简单,只需要选择不同的模式实现不同的加锁和解锁的模板方法即可。
    • AQS提供给独占模式和共享模式的模板方法如下:

														   AQS中提供的主要模板方法,由子类实现🧡💛❤💓💗
	protected boolean tryAcquire(int arg) {				   独占模式下获取锁的方法🧡💛❤💓💗
        throw new UnsupportedOperationException();
    }
    protected boolean tryRelease(int arg) {				   独占模式下解锁的方法🧡💛❤💓💗
        throw new UnsupportedOperationException();
    }
    
    protected int tryAcquireShared(int arg) {			   共享模式下获取锁的方法🧡💛❤💓💗
        throw new UnsupportedOperationException();
    }

    protected boolean tryReleaseShared(int arg) {          共享模式下解锁的方法🧡💛❤💓💗
        throw new UnsupportedOperationException();
    }

    protected boolean isHeldExclusively() {			       判断是否为持有独占锁🧡💛❤💓💗
        throw new UnsupportedOperationException();
    }
  • 在分析了AQS的底层源码和理解其作用以及意义后,我们就看一看其给的模板到底是怎么用的,以ReentrantLock为例;

基于ReetrantLock分析AQS独占锁模式 (JDK15)

  • AQS同步器的实现依赖于内部的 “同步队列(FIFO双向链表队列)完成对同步状态(state)的管理”,队列中的结点由AQS中的内部类Node表示:
    • 当前线程获取锁(同步状态)失败时,AQS会将该线程以及相关等待信息包装成一个 节点(Node) 并将其加入同步队列,同时会阻塞当前线程;
    • (锁)同步状态 释放时,会将头结点head中的线程唤醒,让其尝试获取同步状态(锁);
    • 我们接下来分析一下ReentrantLock中的非公平锁的获取同步状态释放同步状态以及如何加入队列的具体操作;
  • 如何获取公平锁和非公平锁呢?
    public ReentrantLock() {                  			重入锁对的空构造器,直接就是通过非公平锁来进行获取锁等操作💔
        sync = new NonfairSync();
    }
  	public ReentrantLock(boolean fair) {                重入锁的有参构造器构造器,fair-->非公平锁  true-->公平锁💔
        sync = fair ? new FairSync() : new NonfairSync();
    }
    final void lock() {								    加锁操作,虽然lock()不是抽象类,但是initialTryLock()是抽象类,💔
      	if (!initialTryLock())						    具体由子类也就是公平/非公平锁实现;💔
          	acquire(1);	                                acquire(1)再次请求同步状态
    }
  • 重点分析initialTryLock()和acquire(1)(其实核心是里面的tryAcquire(int acquires))方法,这两个方法体现了公平/非公平锁加锁的不同。我们通过下图先简单看一下:
    在这里插入图片描述
  • 通过上图我们可以知道,FairSync和NonFairSync最大的不同之处在于FairSync的两个方法都多了hasQueuedPredecessors()这个方法

ReentrantLock之非公平锁的操作实现过程:

  • 通过前面的图我们可以知道Sync是一个抽象类、继承于AQS,它分别由FairSync、NonfairSync来具体实现,Sync写了一个lock()方法,里面调用的initialTryLock()是一个抽象类,由子类来具体实现,如下:
    @ReservedStackAccess
    final void lock() {
        if (!initialTryLock())
            acquire(1);
    }
  • 这里我们通过非公平锁看的initialTryLock()的实现方式:
    static final class NonfairSync extends Sync {         非公平锁类中有两个方法(如下):🧡
        private static final long serialVersionUID = 7316153563782823691L;

        final boolean initialTryLock() {			   方法1:尝试获取锁,也就是获取同步状态state🧡
            Thread current = Thread.currentThread();             具体操作如下:
            
//当前线程获得了锁,CAS比较并设置state的值,设置成功,则通过AOS(AQS继承类)的方法来设置当前线程持有独占锁,并返回🧡
            if (compareAndSetState(0, 1)) { 						  
                setExclusiveOwnerThread(current); 
                return true;
//如果CAS比较设置失败,则看当前线程是否已经获得锁,在此加锁,重入锁🧡
            } else if (getExclusiveOwnerThread() == current) {
                int c = getState() + 1;                               
                if (c < 0) // overflow	c<0说明不会持有锁,发生错误,具体什么原因可能造成这样不清楚……希望大佬解答🧡					
                    throw new Error("Maximum lock count exceeded");
//设置当前同步状态,当前只有一个线程持有锁,因为不会发生线程安全问题,可以直接执行 setState(c);🧡
                setState(c);	
                return true;									
            } else
                return false;
        }
        /**
         * Acquire for non-reentrant cases after initialTryLock prescreen
         */
        protected final boolean tryAcquire(int acquires) {    //方法2:尝试获得同步状态(锁)🧡
            if (getState() == 0 && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());   //成功则将独占锁线程设置为当前线程 🧡 
                return true;
            }
            return false;
        }
    }
  • (initialTryLock) : 这里获取锁时,首先对同步状态执行CAS操作,尝试把state的状态从0设置为1,如果返回true则代表获取同步状态成功,也就是当前线程获取锁成,可操作临界资源,如果返回false,则表示已有线程持有该同步状态(其值>=1),获取锁失败;
  • 那这里 为什么要对同步状态state进行CAS操作呢?
    • 因为这里存在并发的情景,也就是可能同时存在多个线程同时设置state变量,所以要通过CAS操作保证state变量操作的原子性。
  • 当initialTryLock()返回false,也就是获取锁失败,要执行acquire(1)方法、该方法是AQS中的方法(但是它内部的判断方法不是AQS是由实现类,在此是FairSync、NonfairSync来具体实现的),它对中断不敏感,即使线程获取同步状态失败,进入同步队列,后续对该线程执行中断操作也不会从同步队列中移出(这有兴趣的可以和ReentrantLock中的lockInterruptibly()对比一下,它响应中断就是可以在同步队列中移出,方法如下:
 public final void acquire(int arg) {
        if (!tryAcquire(arg))
            acquire(null, arg, false, false, false, 0L);
  }
  • 为什么参数要传1?
    • 这里传入参数arg表示要获取同步状态后设置的值(即要设置state的值),要获取锁,而state为0时是释放锁,1则是可以获取锁,所以这里一般传递参数为1:
  • 进入acquire(int arg)方法后首先会执行tryAcquire(arg)方法,在前面分析过该方法在AQS中并没有具体实现,而是交由子类实现,因此该方法是由ReetrantLock类内部实现的,也就是FairSync和NonfairSync分别实现了tryAcquire(arg)方法
  • 继续分析:
    • 如果tryAcquire(arg)返回true,acquire自然不会执行,这是最理想的,因为毕竟当前线程已获取到锁
    • 如果tryAcquire(arg)返回false,则会执行acquire(null, arg, false, false, false, 0L);
    • 到这里,也就是说:当一个线程通过 final boolean initialTryLock() 没有请求到同步状态,再通过tryAcquire(int acquires)也没有请求到同步状态,这时候才执行的acquire(null, arg, false, false, false, 0L)
    • 分析这个方法前我们先考虑两个问题:
    • 为什么这两个请求同步的方法先用的initialTryLock() 再tryAcquire(int acquires)? 🤣🤣🤣🤣🤣🤣
      • 个人认为initialTryLock()更突出的是一个重入锁的功能,当没有获取到锁,也就压根不可能有重入的机会,所以调用tryAcquire(int acquires)就可以了。
      • 这也可以看出,加锁的相关操作我们通过子类(这里用的非公平锁)来重写( 即initialTryLock()和tryAcquire(int acquires)方法)
    • 我们通过几次锁请求后,才进行入队操作? 答:两次!!!!!(哪两次不用我说了吧)
    • 我们接下来分析两次锁请求失败后执行的这个方法(入队前的操作以及入队后操作):💥💥💥


JDK15版本(没分析完):

  • 将入队前的操作,入队后的操作,以及退出同步队列都合并成下面一个方法:和早期的JDK8的方法有了很大变化,因此在这里我先根据别的博客分析JDK8左右的相关操作,然后有能力再分析15,性能上是有优化,细节上有处理,但是大致流程不变,所以以后再写这一部分(时间有限,有兴趣的可以看回头来看)(看添加的博客地址吧)
 /	**
     * Main acquire method, invoked by all exported acquire methods.   主要的获取方法,由所有导出的获取方法调用🧡
     *
     * @param node  		  //除非是重新获取condition条件,否则为空🧡
     * @param arg 			  //加锁次数🧡
     * @param shared 		  //控制是否时共享线程队列也就是SharedNode的布尔值🧡
     * @param interruptible   //是否时可中断线程🧡
     * @param timed			 //是否由最长等待时间🧡
     * @param time    	     //中断超时时间🧡
     *      //获得state时为正值,超时时为0,中断时为负值🧡🧡
     * @return positive if acquired, 0 if timed out, negative if interrupted 
     */
final int acquire(Node node, int arg, boolean shared, boolean interruptible, boolean timed, long time) {
	 // 带入参数:null, arg, false, false, false, 0L💜💜💜💜💜     独占锁,不可中断,不会超时
	
        Thread current = Thread.currentThread();                // 获取当前线程💗
        byte spins = 0, postSpins = 0;   // 自旋变量和之前的自旋变量postSpins💗
        boolean interrupted = false, first = false; // 中断变量值interrupted,first表示第一次进入方法💗
        Node pred = null;               // 存储前置节点💗

		// 自旋获取锁
        for (;;) {    
      // 循环的第一次判断为 true && false (node为null pred=null,null!=null。返回false就不执行后面的赋值和判断)
      // 自旋后的第二次由于是刚创建的则prev为null因此还是false,pred=null,还是false不执行
     // 当node!=null且node.prev!=null说明节点已经入队了,因此第二个判断返回true需要判断!(first = (head == pred))返回的是false
            if (!first && (pred = (node == null) ? null : node.prev) != null &&!(first = (head == pred))) {
            // 进入这个代码块说明队列不为空且不止一个线程在等待
                if (pred.status < 0) {		 // 实际就是传入的node的前一个节点的status是否<0	         
                    cleanQueue();        // 如果<0,则清空队列
                    continue;
                } else if (pred.prev == null) {
                    Thread.onSpinWait();    // 自旋等待确保序列化
                    continue;
                }
            }
       //循环第一次 false || true,需要进入代码块。pred在第一次判断就被赋值为null,循环进入前也是null
            if (first || pred == null) {
                boolean acquired;
                try {
                    if (shared)
                        acquired = (tryAcquireShared(arg) >= 0);// 这个方法由Semaphore调用,意思判断剩余信号量是否>=0
                    else
                     /**
                         * 尝试抢占锁,如果没有抢到则会自旋,tryAcquire(arg);会一直重复调用,直到抢占成功
                         * 子类实现的方法。例如非公平锁的tryAcquired(1);
                         * 如果state为0,则acquired则会变成true。将当前线程设置为独占并更新state为arg
                         * 如果state不为0,则acquired=false,说明被其它线程上锁了
                         *
                         * 自旋的起点是后面
                         * if (node == null) {
                         *     if (shared)// 如果是共享队列节点则创建SharedNode,然后由于后面没有代码则会自旋for循环重新执行一遍只是这个时候node不为null
                         *        node = new SharedNode(); //这个是线程队列的头节点,用来标识这个队列是一个共享锁线程等待队列
                         *     else       // 如果是一个排他锁创建一个排他节点
                         *        node = new ExclusiveNode();// 线程队列的头节点,标识是一个排他锁线程队列
                         * }
                         * 然后到这里的tryAcquire(arg);一直原地踏步,直到抢占到锁
                         * acquired更新为true
                         * 进入catch后面的if
                         *
                         */
         // 会回到子类(NonfairSync、FairSync等)的实现的方法尝试获得锁,如果失败则还是false,直到成功true
                        acquired = tryAcquire(arg);
                } catch (Throwable ex) {
                    cancelAcquire(node, interrupted, false);
                    throw ex;
                }
                 // true说明当前线程抢占到锁了
                if (acquired) {//信号量的使用一个信号量,如果成功也会进行判断,使用信号量成功则进入,没有成功说明信号量使用完了
                    if (first) {
                    if (first) {
                        node.prev = null;
                        head = node;
                        pred.next = null;
                        node.waiter = null;
                        if (shared)
                            signalNextIfShared(node);
                        if (interrupted)
                            current.interrupt();
                    }
                    return 1;// 返回1标签抢占到锁了这是这个方法的唯一结束点,其它分支始终都会死循环
                }
            }
             /**
             *
             * AQS队列的形成起点,头节点就是下面的SharedNode或ExclusiveNode,然后自旋
             *
             */
            // 第一次进入node传入的是null因此进入
            // 第二次由于第一次进入后node=new SharedNode();或node = new ExclusiveNode();则这个if就不会在进入,会进入第二个if因为pred=null
            if (node == null) {  // 形成头节点
                if (shared)	// 如果是共享队列节点则创建SharedNode,然后由于后面没有代码则会自旋for循环重新执行一遍只是这个时候node不为null
                    node = new SharedNode(); //这个是线程队列的头节点,用来标识这个队列是一个共享锁线程等待队列
                else // 如果是一个排他锁创建一个排他节点
                    node = new ExclusiveNode();//线程队列的头节点,标识是一个排他锁线程队列			
            } else if (pred == null) {         //try to enqueue 尝试进入同步队🧡🧡🧡🧡🧡🧡
                node.waiter = current;	 将当前线程放到node中,也就是当前结点中🧡(如果不进队,都不用放入到当前结点)🧡中)
                Node t = tail;				   判断尾部结点🧡
                node.setPrevRelaxed(t);          avoid unnecessary fence,应该是一种性能的优化,Native操作,❔❔❔
                if (t == null)				   //如果当前尾部结点为空,说明还没有同步队列,需要创建一个同步队列🧡
                    tryInitializeHead();	   //tryInitializeHead()就是用来创建同步队列的🧡
                else if (!casTail(t, node))    //如果尾部节点不为空,则将尾部结点替换成node🧡
                    node.setPrevRelaxed(null);   back out 如果替换不成功,按以前的JDK是要循环,再次尝试,但这里是
                else							 退出了?感觉不像❔❔❔
                    t.next = node;				//casTail(t, node)替换成功,因为双向链表,所以要指向尾部结点
                    							//到这里就进入同步队列了🧡🧡🧡🧡🧡🧡🧡
            } else if (first && spins != 0) {
                --spins;                        // reduce unfairness on rewaits  自旋操作
                Thread.onSpinWait();
            } else if (node.status == 0) {		
                node.status = WAITING;          // enable signal and recheck
            } else {
                long nanos;
                spins = postSpins = (byte)((postSpins << 1) | 1);
                if (!timed)
                    LockSupport.park(this);
                else if ((nanos = time - System.nanoTime()) > 0L)
                    LockSupport.parkNanos(this, nanos);
                else
                    break;
                node.clearStatus();
                if ((interrupted |= Thread.interrupted()) && interruptible)
                    break;
            }
        }   
        return cancelAcquire(node, interrupted, interruptible);    最终都没能获取同步状态,结束该线程的请求❤
    }

JDK8版本(基于大神的blog进行分析):

  • 先看一下JDK8对应的acquire方法:
   public final void acquire(int arg) {						//JDK15的acquire😄😄😄😄
        if (!tryAcquire(arg))
            acquire(null, arg, false, false, false, 0L);
  }
  public final void acquire(int arg) {						//JDK8的acquire😄😄😄😄
    //再次尝试获取同步状态
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))      //Node.EXCLUSIVE对应的独占锁🧡
        selfInterrupt();
}
  • 这里的 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)的效果 其实就是上面JDK15acquire(Node node, int arg, boolean shared, boolean interruptible, boolean timed, long time)的效果:我们按JDK8(别问为什么!问就是菜!)来分析:
private Node addWaiter(Node mode) {							
    Node node = new Node(Thread.currentThread(), mode);  //将请求同步状态失败的线程封装成结点🧡

    Node pred = tail;
   	//如果是第一个结加入肯定为空,尾部结点一定为null,直接进入队列即可。🧡
    if (pred != null) {		//如果非第一个结点则直接执行CAS入队操作,尝试添加到尾部🧡							
     	//同步队列中双向链表的尾部的具体操作:🧡	
        node.prev = pred;					
        if (compareAndSetTail(pred, node)) {//使用CAS执行尾部结点替换,尝试在尾部快速添加,如果CAS失败,执行enq🧡
            pred.next = node;	//为什么CAS会失败呢?因为这时候可能有多个线程包装成的结点放入到队尾🧡
            return node;
        }
    }
    /*
    	入队的两种情况:😃😃😃😃
    	1、如果是第一个结点则直接加入,也就是执行enq入队操作,顺便在enq方法中创建同步队列😃😃
    	2、如果不是第一个结点,则说明队列存在,但是在上面的CAS操作没有成功替换尾结点,则执行enq入队操作😃😃
    */										   
    enq(node); 
    return node;	//返回同步队列尾部结点
}

💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜
private Node enq(final Node node) {
    for (;;) {										//死循环,return跳出🧡
         Node t = tail;
		 //对应入队的第一种情况:初始化 如果尾部结点为null,即没有头节点和尾结点,也就是没同步队列,需要创建头节点😃😃									        
         if (t == null) { 
             if (compareAndSetHead(new Node()))  //创建并使用CAS设置头结点🧡
                 tail = head;
      	//对应入队的第二种情况:通过CAS在队尾添加新结点,并设置新的队尾😃😃
         } else {								   
             node.prev = t;
             if (compareAndSetTail(t, node)) {
                 t.next = node;   
                 return t;   //这里的返回没有意义!!只是为了将结点添加到尾部后(维持这个同步队列),退出这个死循环❤
             }
         }
     }
    }
💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜
  • 总结addWaiter方法的作用有两个:
    • 让申请同步状态失败的线程入队;
    • 入队后维护队列的链表关系;
    • 细节处理:主要针对队列是否有初始化,没有初始化则new一个新的Node作为队首,然后重新入队;
  • 发现:在前面的请求同步状态有两次机会,以及请求同步状态失败后我们将结点添加到尾部的操作也有两次机会(如果有队列,则先CAS操作加入队尾,不成功则执行eng()方法进一步CAS)(虽然第二次机会一定会添加到同步队列尾部)!!!
  • 以上就是一个入队前的操作,那当入队后做什么呢(自旋!!)?别急,我们慢慢看!!!🥱🥱
  • 我们再次看auquire方法
public final void acquire(int arg) {
    //再次尝试获取同步状态
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  • 当执行完addWaiter(Node.EXCLUSIVE), arg),也就是添加到同步队列后,将要进行一个自旋过程,就是说每个结点都在观察时机是否成熟(也就是说是否可以获取同步状态的时候),我们从同步队列退出并结束自旋;
  • 接下来我们就看一下自旋过程到底怎么执行的:acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
/*
	@node:addWaiter(Node mode)方法返回的尾部结点
	@arg:一般为1,代表state状态,同意去抢夺锁	
*/
final boolean acquireQueued(final Node node, int arg) {	  //这里node是队列的尾部节点
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {			//自旋,死过程💚
         
            final Node p = node.predecessor();	//获取前驱结点p💚
           	//当且仅当前驱节点为头结点head才尝试获取同步状态,然后来判断头节点是否释放同步状态💚
            if (p == head && tryAcquire(arg)) {
                setHead(node);	//当获取了同步状态state==1,也就是头节点已经释放了同步状态,才可将node设置为头结点💚
                									//设置完头节点后的操作:3步😊😊😊
                p.next = null; 							//1、清空原来头结点的引用(next)便于GC😊
                failed = false;							//2、说明已经获得了同步状态,不需要自旋😊
                return interrupted;						//3、返回中断标志,也就是退出自旋😊
            }
	        /*
				判断挂起的两种情况:😃😃     	
	        	1、前驱结点不是head;😃
	        	2、前驱节点是头节点head但是它还没释放同步状态,也就是当前节点获取同步状态失败,则判断是否挂起线程;😃
	           (*****)注意:大部分结点执行这个方法其实首先都是先挂起,只有前结点为头节点才有申请同步状态的相关操作💚
	        */
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);	//最终都没能获取同步状态,结束该线程的请求💚
    }
}
  • acquireQueued(final Node node, int arg)方法的总结:
    • 当前线程在自旋(死循环)中获取同步状态,当且仅当前驱结点为头结点(head)才尝试获取同步状态,这符合FIFO的规则,也就是先进先出;
    • 注意head是当前获取同步状态(state==1) 的线程结点,只有当head释放同步状态,后继结点才能去获取到同步状态,因此后继结点在其前继结点为head时,才进行尝试获取同步状态
    • 问:在自旋过程中什么时候会被挂起?(两种情况)🧡🧡🧡🧡
      • 首先是前驱结点不是head,这时会挂起;
      • 其次就是前驱节点是头节点head,但是head还没有释放同步状态,也就是当前结点获取同步状态失败,这时候也考虑挂起;
    • 我们看一下acquireQueued(final Node node, int arg)这个方法的其他方法:
    • 进入if语句后调用setHead(node)方法,将当前线程结点设置为head;我们看一下这个setHead()方法:
//设置为头结点
private void setHead(Node node) {
        head = node;
 			//清空结点数据🧡
        node.thread = null;
        node.prev = null;
}
  • setHead(Node node)方法分析:

    • 当node结点被设置为head后,其thread信息和前驱结点将被清空,为什么呢?
      • 因为该线程已获取到同步状态(也就是获得了锁,个人认为是获得同步状态就可以去找锁,因为state==1,这时没有线程拥有锁、所以也就变向获得了锁),正在执行了,也就没有必要存储相关信息了;
    • 问:同步队列的head里面的Thread永远为null嘛?
      • 是的!!因为head中的线程已经获得了同步状态,所以这个线程没必要在保存在head中。
    • head保存waitStatus和指向后继结点的指针即可
      • waitStatus: 是为了进行判断是否挂起,唤醒等判断的(通过shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()方法);
      • 指向后继结点指针: 便于head结点释放同步状态后唤醒后继结点,
      • 执行结果如下图(别人的图、地址在文章头):
        在这里插入图片描述
  • 从图可知:

    • 更新head结点的指向,将后继结点的线程唤醒并获取同步状态,调用setHead(node)将其替换为head结点,清除相关无用数据。
    • 当然如果前驱结点不是head,或者前驱节点是head但是还没有释放同步状态,就判断是否要挂起
  • 接下来我们看一下是否挂起的具体操作:

	if (shouldParkAfterFailedAcquire(p, node) &&   parkAndCheckInterrupt())
    		interrupted = true;
 🧡🧡🧡🧡🧡🧡🧡🧡 🧡🧡🧡🧡🧡🧡🧡🧡	 🧡🧡🧡🧡🧡🧡🧡🧡	 🧡🧡🧡🧡🧡🧡🧡🧡	 🧡🧡🧡🧡🧡🧡🧡🧡			
   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        			//pred是node的前驱节点,但是pred不是头节点,因为如果是头节点,node就不用挂起了:💜
        int ws = pred.waitStatus;						//获取前驱节点的等待状态💜💜💜
       
        if (ws == Node.SIGNAL)							//如果为等待唤醒(SIGNAL)状态则返回true💜💜💜
            return true;
        if (ws > 0) { //如果ws>0 则说明是结束状态,遍历前驱结点直到找到不是结束状态的结点💜💜💜
            do {										
                node.prev = pred = pred.prev;		
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { 
//如果ws小于0又不是SIGNAL状态,一般是从等待队列中刚转过来,是Condition状态 则将其设置为SIGNAL状态,代表该结点的线程正在等待唤醒。💜💜💜
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
		//只有当前驱节点是唤醒状态,才挂起当前结点
	private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);						//将当前线程挂起💜💜💜
       										 //获取线程中断状态,interrupted()是判断当前中断状态,💜💜💜
      										  //并非中断线程,因此可能true也可能false,返回💜💜💜
        return Thread.interrupted();
}
  • 分析:

    • shouldParkAfterFailedAcquire() 方法的作用(三种情况的判断)
      • 判断当前结点的前驱结点是否为SIGNAL状态(即等待唤醒状态),如果是则返回true。
      • 如果前驱节点的ws为CANCELLED状态(值为1>0),即结束状态,则说明该前驱结点已没有用应该从同步队列移除,执行while循环,直到寻找到非CANCELLED状态的结点。
      • 倘若前驱结点的ws值既不为CANCELLED,也不为SIGNAL(当从Condition的条件等待队列转移到同步队列时,结点状态为CONDITION因此需要转换为SIGNAL),那么将其转换为SIGNAL状态,等待被唤醒。
    • parkAndCheckInterrupt() 方法的作用:
      • 若shouldParkAfterFailedAcquire()方法返回true,即当前结点的前驱结点为SIGNAL等待唤醒状态同时又不是head结点,那么使用parkAndCheckInterrupt()方法挂起当前线程,称为WAITING状态,需要等待一个unpark()操作来唤醒它
  • 注意:park()和unpark()方法都是unsafe类的native方法。(LockSupport是系统原语)

  • 到此ReetrantLock内部间接通过AQS的FIFO的同步队列就完成了lock()操作,这里我们总结成逻辑流程图:
    在这里插入图片描述
    在这里插入图片描述

  • 以上基于ReentrantLock类的加锁方式就都结束了


lockInterruptibly()/tryLock()方法和lock()的源码级对比:

  • 我们基于JDK8再看一下ReentrantLock类的lockInterruptibly()或者tryLock()方法,最终它们都间接调用到doAcquireInterruptibly()
 private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; 											// 帮助GC🧡
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                 
          🧡🧡🧡🧡   throw new InterruptedException();🧡🧡🧡		 直接抛异常,中断线程的同步状态请求,最大的不同🧡🧡
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 最大的不同:
    • 如果当前线程包装的结点在同步队列中的前驱节点不是头结点head,其状态还是signal等待唤醒状态,并且检测到线程的中断操作后,直接抛出异常,从而中断线程的同步状态请求,移除同步队列!!!!!
    • 而遇到这种情况,lock()不是直接抛出异常,而是挂起并设置中断标志为true,直到其前驱节点是头节点,并释放同步状态,或线程被中断,则再次进入自旋

ReentrantLock之公平锁的操作实现过程:

  • 刚总结了基于ReentrantLockNonfairSync加锁操作,现在我们看一下基于 ReetrantLock的 FairSync 加锁操作
  • 公平锁对于锁的获取顺序是完全遵循时间上的FIFO规则,也就是说先到先得,不能插队,接下来我们通过类结构进一步分析:
  • 在这里插入图片描述
  • 通过上图我们可以发现,ReentrantLock的lock()方法,以及NonfairSync和FairSync下有两个相同的方法,我们源码分析:
    /* 
    	Sync类的lock()方法:通过initialTryLock()以及acquire(1)中的tryAcquire(int acquires)由具体
    	实现类(公平非公平锁)的实现来达到不同的效果🧡 🧡 🧡 
    */
        @ReservedStackAccess 					
        final void lock() {						
            if (!initialTryLock())			
                acquire(1);
        }
        abstract boolean initialTryLock();
🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡
   	    // 💛💛FairSync的tryAcquire(int acquires)方法💛💛
       	protected final boolean tryAcquire(int acquires) {     
            if (getState() == 0 && !hasQueuedPredecessors() &&		
                compareAndSetState(0, acquires)) {				   	
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
		// 💛💛NonfairSync的tryAcquire(int acquires)方法💛💛
        protected final boolean tryAcquire(int acquires) {	
            if (getState() == 0 && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
   
🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡        
     	 	 //💛💛FairSync的initialTryLock()方法💛💛
        final boolean initialTryLock() {   					
            Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (getExclusiveOwnerThread() == current) {
                if (++c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            }
            return false;
        }
        
     	  // 💛💛NonfairSync的initialTryLock()方法💛💛
        final boolean initialTryLock() {					
            Thread current = Thread.currentThread();
            if (compareAndSetState(0, 1)) { // first attempt is unguarded
                setExclusiveOwnerThread(current);
                return true;
            } else if (getExclusiveOwnerThread() == current) {
                int c = getState() + 1;
                if (c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            } else
                return false;
        }  
🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡 🧡
  • 分析:
    • 通过FairSyncNonfairSync类的两个具体实现的方法的源码对比:
      • 我们可以得知FairSynctryAcquire(int acquires)initialTryLock()NonfairSync的这两个方法,前者在通过CAS方法尝试设置state值前,调用了 AQS的hasQueuedThreads() 方法;

FairSync和NonfairSync的最大区别:!!!!

    public final boolean hasQueuedThreads() {
        for (Node p = tail, h = head; p != h && p != null; p = p.prev)
   //status<0的时候,就是结束状态(注意区分JDK15和JDK8的区别,这是JDK15的方法)😃😃
            if (p.status >= 0)  
                return true;   
        return false;
    }  
  • 分析:
    • 通过 hasQueuedThreads() 判断**同步队列是否存在结点,如果存在必须先执行完同步队列中结点的线程;**
    • 这就是非公平锁与公平锁最大的区别:🧡🧡🧡🧡
      • 公平锁在线程请求到来时先会判断同步队列是否存在结点(状态>=0的),如果存在先执行同步队列中的结点中的线程,当前线程将封装成node加入同步队列等待。
      • 非公平锁,当线程请求到来时,不管同步队列是否存在线程结点,直接尝试获取同步状态,获取成功直接访问共享资源;
    • 注意:在绝大多数情况下,非公平锁才是我们理想的选择,毕竟从效率上来说非公平锁总是胜于公平锁。

ReentrantLock(公平锁、非公平锁)释放锁的操作实现过程:

  • 由上面的总结我们可以得知,ReentrantLock获取锁是由具体类实现,而释放锁则没有那么复杂,是统一的,下面我们来看一下:
    public void unlock() {   					//ReentrantLock类的unlock方法🧡
        sync.release(1);
    }
    AQS的release()方法🧡🧡
    public final boolean release(int arg) {  	//sync.release()方法,是ReentrantLock内部类Sync的方法,然后Sync继承
       	//尝试释放锁
        if (tryRelease(arg)) {					//于AQS,而Sync没有重写release()方法,因此这是AQS的实现方法
       
            signalNext(head);					//通过另一方面说明、AQS实现了最核心、最基本的方法、但是类似具体加锁
       
            return true;						//释放锁等就由相应的实例类来实现(底层细节还是AQS实现);🧡🧡🧡
        }
        return false;
    }
 🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡 🧡🧡🧡🧡🧡🧡 
    
    ReentrantLock类中的内部类Sync实现的tryRelease(int releases)----》尝试释放锁的操作💛
     @ReservedStackAccess
      protected final boolean tryRelease(int releases) {
          int c = getState() - releases;					释放锁,一般getState()1,releases为1💛
          if (getExclusiveOwnerThread() != Thread.currentThread())
              throw new IllegalMonitorStateException();
          boolean free = (c == 0);
          if (free)										判断状态是否为0,如果是则说明已经释放同步状态,可以释放锁了💛
 // 先释放同步状态,再释放锁,虽然时间段但是也有时间差,可能造成同步队列中头节点的后驱结点获取同步状态state==1后
 //,但是不能得到锁,上面有提到					
              setExclusiveOwnerThread(null);				设置Ownernull,也就是释放锁了(不让线程持有锁了)💛
          setState(c);										设置更新同步状态💛
          return free;
      }
    //释放同步状态的操作相对简单些,tryRelease(int releases)方法是ReentrantLock类中内部类自己实现的,
    //因为AQS对于释放锁并没有提供具体实现,必须由子类自己实现。
    //释放同步状态后会使用 signalNext(head) 唤醒后继结点的线程,因此我们看一下这个方法
    
 🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡 🧡🧡🧡🧡🧡🧡  
    AQS的signalNext(Node h)方法---》释放锁后唤醒后继结点的线程的操作:💛
    private static void signalNext(Node h) {        当头节点不为空,且后驱结点不为空,将其状态设置成WAITING💛
        Node s;										然后唤醒(unpark())最前边未放弃的线程;
        if (h != null && (s = h.next) != null && s.status != 0) {
            s.getAndUnsetStatus(WAITING);
            LockSupport.unpark(s.waiter);
        }
    }
 🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡   🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡  🧡🧡🧡🧡🧡🧡 🧡🧡🧡🧡🧡🧡  
     LockjSupportunpark(Thread thread)方法 ---》唤醒同步队列中最前边未放弃的线程(也就是状态不为CANCELLED的线程结点s)💛
     
     public static void unpark(Thread thread) {
        if (thread != null)
            U.unpark(thread);
    }    
  • 总结:
    • 线程获取锁失败,将线程调用addWaiter()封装成Node进行入队操作。addWaiter()中enq()方法完成对同步队列的头节点初始化以及CAS尾插失败后的重试处理。
    • 入队之后排队获取锁的核心方法acquireQueued(),节点排队获取锁是一个自旋过程。当且仅当当前节点的前驱节点为头节点并且获取同步状态时,节点出队并且该节点引用的线程获取到锁。否则不满足条件时会不断自旋将前驱节点的状态置为SIGNAL后调用LockSupport.part()将当前线程阻塞。
    • 释放锁时会唤醒后继结点(后继结点不为null)。

总体来看,重入锁ReentrantLock,是一个基于AQS并发框架的并发控制类,其内部实现了3个类:

  • Sync类 : Sync继承自AQS,实现了释放锁的模板方法tryRelease(int)
  • NonfairSync类: 实现获取锁的方法tryAcquire(int);
  • FairSync类,实现获取锁的方法tryAcquire(int);
  • ReentrantLock的所有方法实现几乎都间接调用了这3个类,因此当我们在使用ReentrantLock时,大部分使用都是在间接调用AQS同步器中的方法,以上就是ReentrantLock的实现原理。



Condition接口及原理 (JDK8)

  • 在并发编程中,每个Java对象都存在一组监视器方法,如wait()、notify()以及notifyAll()方法,通过这些方法,我们可以实现线程间通信与协作(也称为等待唤醒机制),但是这些方法必须配合着synchronized关键字使用;但是随着Condition的出现,我们发现与synchronized的等待唤醒机制相比 Condition具有更多的灵活性以及精确性,这是因为notify()在唤醒线程时是随机(同一个锁),而Condition则可通过多个Condition实例对象建立更加精细的线程控制,也就带来了更多灵活性了,简单来说:相关内容
    • 通过Condition能够精细的控制多线程的休眠与唤醒
    • 对于一个锁,我们可以为多个线程间建立不同的Condition。
  • 那Condition到底如何运用呢?又是怎么样的一个继承结构呢?
    • 在这里插入图片描述
  • 通过上图我们可以发现,AQS以及支持64位的AQS类都分别通过 内部类ConditionObject 来实现了这个Condition接口
  • 我们再看一下Condition接口中有什么方法?
public interface Condition {
	 /**
	  * 当前线程进入等待状态直到被通知(signal)或中断
	  * 当其他线程调用singal()或singalAll()方法时,该线程将被唤醒
	  * 当其他线程调用interrupt()方法中断当前线程
	  * await()相当于synchronized等待唤醒机制中的wait()方法
	  */
    void await() throws InterruptedException;		
	//当前线程进入等待状态,直到被唤醒,该方法不响应中断🧡
    void awaitUninterruptibly();		
	//调用该方法,当前线程进入等待状态,直到被唤醒或被中断或超时;其中nanosTimeout指的等待超时时间,单位纳秒🧡
    long awaitNanos(long nanosTimeout) throws InterruptedException;
	//同awaitNanos,但可以指明时间单位🧡
    boolean await(long time, TimeUnit unit) throws InterruptedException;
	//调用该方法当前线程进入等待状态,直到被唤醒、中断或到达某个时间期限(deadline),
	//如果没到指定时间就被唤醒,返回true,其他情况返回false🧡
    boolean awaitUntil(Date deadline) throws InterruptedException;
	//唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获取与Condition相关联的锁,功能与notify()相同🧡
    void signal();
	//唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获取与Condition相关联的锁,功能与notifyAll()相同
    void signalAll();
}
  • 前面已经分析过,AQS中存在两种队列:
    • 同步队列: 同步队列中的结点由Node(ExclusiveNode、SharedNode继承Node)类组成
    • 等待队列: 等待队列中的结点也由Node(ConditionNode继承Node)组成、但是使用前必须获得锁,其结点的waitStatus的值为CONDITION。
    • 在这里插入图片描述
  • 我们具体来看一下这个内部类ConditionObject中的属性:
    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
       	//等待队列的第一个等待结点🧡
        private transient ConditionNode firstWaiter; 
        /** Last node of condition queue. */
        //等待队列的最后一个等待结点🧡
        private transient ConditionNode lastWaiter; 
        /**
         * Creates a new {@code ConditionObject} instance.
         */
        public ConditionObject() { }		 //空构造器
        //.....其他代码
 ===========================================================================================
		 /* Mode meaning to reinterrupt on exit from wait */
		private static final int REINTERRUPT =  1; 		// 从等待状态切换为中断状态
		/** Mode meaning to throw InterruptedException on exit from wait */
		private static final int THROW_IE    = -1; 		// 抛出异常标识

  • 在实现类ConditionObject中有两个结点分别是firstWaiterlastWaiter

    • firstWaiter: 代表等待队列第一个等待结点,
    • lastWaiter: 代表等待队列最后一个等待结点;
  • 每个Condition都对应着一个等待队列,也就是说如果:

    • 一个锁上创建了多个Condition对象,那么也就存在多个等待队列
  • 等待队列是一个FIFO的队列,在队列中每一个节点都包含了一个线程的引用,而该线程就是Condition对象上等待的线程。

  • 当一个线程调用了await() 相关的方法,那么该线程将会释放锁,并构建一个Node节点封装当前线程的相关信息加入到等待队列中进行等待,直到被唤醒、中断、超时才从队列中移出。

  • 看一下ConditionNode类中的属性:

    • ConditionNode nextWaiter; // link to next waiting node 说明是单向链表
  • Condition中的等待队列模型:
    在这里插入图片描述

  • 如图所示:

    • Node节点的数据结构,在等待队列中使用的变量与同步队列是不同的;
    • Condtion中等待队列的结点只有直接指向后继结点并没有指明前驱结点,相当于单链表;而且使用的变量是nextWaiter而不是next,这点我们在前面分析结点Node的数据结构时讲过。firstWaiter指向等待队列的头结点,lastWaiter指向等待队列的尾结点,等待队列中结点的状态只有两种,CANCELLED和CONDITION
      • CANCELLED: 表示线程已结束需要从等待队列中移除;
      • CONDITION: 表示条件结点等待被唤醒。
    • 注意: AQS中只能存在一个同步队列,但可拥有多个等待队列( 因为每个Codition对象对应一个等待队列)。

JDK15的await()方法

  • 下面从源码分析:看看被调用ConditionObject中的await()方法的线程是如何加入等待队列的,而又是如何从等待队列中被唤醒的;(JDK15的分析不懂,看JDK8的吧)
   public final void await() throws InterruptedException {
        if (Thread.interrupted())									//判断持有锁的线程是否被中断	🧡🧡	
            throw new InterruptedException();						//没有中断,继续往下执行
            
        ConditionNode node = new ConditionNode();					//接下来准备创建新结点加入等待队列并返回 🧡🧡	
        
        long savedState = enableWait(node);							//释放当前线程锁即释放同步状态🧡🧡	
        LockSupport.setCurrentBlocker(this); // for back-compatibility
        boolean interrupted = false, cancelled = false;
        
        while (!canReacquire(node)) {							//判断结点是否在同步队列(SyncQueue)中,即是否被唤醒🧡🧡	
            if (interrupted |= Thread.interrupted()) {
                if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
                    break;              // else interrupted after signal
            } else if ((node.status & COND) != 0) {
                try {
                    ForkJoinPool.managedBlock(node);
                } catch (InterruptedException ie) {
                    interrupted = true;
                }
            } else
                Thread.onSpinWait();    // awoke while enqueuing
        }
        LockSupport.setCurrentBlocker(null);
        node.clearStatus();
        acquire(node, savedState, false, false, false, 0L);
        if (interrupted) {
            if (cancelled) {
                unlinkCancelledWaiters(node);
                throw new InterruptedException();
            }
            Thread.currentThread().interrupt();
        }
    }

JDK8的await()方法

public final void await() throws InterruptedException {
    // 如果当前线程在调动await()方法前已经被中断了,则直接抛出InterruptedException
    if (Thread.interrupted())
        throw new InterruptedException();
    // 先清理等待队列中不是CON的节点,然后将当前线程封装成Node添加到条件队列
    Node node = addConditionWaiter();			1
    // 释放当前线程所占用的锁,并将状态设为CAN
    int savedState = fullyRelease(node);		2
    int interruptMode = 0;
    // 如果当前队列不在同步队列中,说明刚刚被await, 还没有人调用signal方法,则直接将当前线程挂起
    while (!isOnSyncQueue(node)) {				3
        LockSupport.park(this); // 线程将在这里被挂起,停止运行
        // 能执行到这里说明要么是signal方法被调用了,要么是线程被中断了
        // 所以检查下线程被唤醒的原因,如果是因为中断被唤醒,则跳出while循环
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  4
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();  💚💚
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡
// 执行addConditionWaiter() , 添加到等待队列。💛  
private Node addConditionWaiter() {           1 方法
	Node t = lastWaiter; // 获取尾部指针,看来是采用尾插法      t是等待队列的尾节点💛
	// If lastWaiter is cancelled, clean out.    
	if (t != null && t.waitStatus != Node.CONDITION) {       //如果队列不为空且尾部节点不为CON状态,则清除这个点 
		unlinkCancelledWaiters();       💚💚
		t = lastWaiter;    
	}    
	Node node = new Node(Thread.currentThread(), Node.CONDITION); // 创建一个新节点,并设置为CONDITION状态💛 
	if (t == null) // 尾结点为空,说明队列是空的        
		firstWaiter = node; // 初始化队列    
	else        
		t.nextWaiter = node; // 尾插    
	lastWaiter = node; // 调整尾指针指向    
	return node; // 返回新增节点对象
}

1.1 在await()和addConditionWaiter()方法中,都调用了 unlinkCancelledWaiters(),先看一下它做了什么:

🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡
private void unlinkCancelledWaiters() {
    Node t = firstWaiter; // 拿到头节点
    Node trail = null;
    while (t != null) { // 如果头节点不为空,队列不为空
        Node next = t.nextWaiter; // 遍历等待队列
        if (t.waitStatus != Node.CONDITION) { // 如果节点的状态不是CONDITION
            t.nextWaiter = null; // 将节点移除队列
            if (trail == null) // 首次遍历,进度为0
                firstWaiter = next; // 头节点指向被移除节点的下一个节点
            else
                trail.nextWaiter = next; // 进度指向下一个节点,也是将修复被移除队列节点的影响,保证队列连续
            if (next == null)
                lastWaiter = trail; // 如果next为空,说明队列遍历完成,将尾指针指向进度节点
        }
        else // 如果节点的状态是CONDITION
            trail = t; // 保存进度
        t = next;
    }
}

1 addConditionWaiter()的作用就是将先通过“尾插法”将等待队列中节点状态不为CON的节点去除 (unlinkCancelledWaiters),然后将节点加入到等到等待队列尾部

unlinkCancelledWaiters() 方法就做了一件事,遍历等待队列,将非CONDITION状态的节点移除

2 方法:

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState(); // 获取AQS中的state值
        if (release(savedState)) { /// 调用释放锁方法
            failed = false; 
            return savedState; // 如果释放成功,返回state值
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)	//如果释放状态成功,则将节点设为CAN
            node.waitStatus = Node.CANCELLED;
    }
}

在fullyRelease()方法中,主要是调用了release()去释放锁。这里有个前提就是线程必须先持有锁,才能调用await()方法,进而release()释放锁。那么就引出了await()方法暂停线程,会导致锁被释放的逻辑。

2.1 方法release():

public final boolean release(int arg) {
  if (tryRelease(arg)) { // 同样先让子类执行tryRelease()来尝试释放锁,
    // 这回是如果成功了继续
    Node h = head;
    if (h != null && h.waitStatus != 0)
      // head如果不为null并且状态不是0,则调用unparkSuccessor()   (唤醒同步对垒)
      unparkSuccessor(h);
    return true;
  }
  return false;
}

释放锁具体怎么释放,交给了子类去操作,那么说明一点,tryRelease()中并不涉及同步队列的操作。而unparkSuccessor(h);中做的事情,才是AQS的关键。

private void unparkSuccessor(Node node) {
  /*
   * If status is negative (i.e., possibly needing signal) try
   * to clear in anticipation of signalling.  It is OK if this
   * fails or if status is changed by waiting thread.
   */
  int ws = node.waitStatus;
  if (ws < 0)
    compareAndSetWaitStatus(node, ws, 0);
  // 修改node节点状态为SIGNAL

  /*
   * Thread to unpark is held in successor, which is normally
   * just the next node.  But if cancelled or apparently null,
   * traverse backwards from tail to find the actual
   * non-cancelled successor.
   */
  Node s = node.next;
  if (s == null || s.waitStatus > 0) { // head的下一个节点,如果为CANCEL状态
    s = null;
    // 清理同步队列,注意这里是从尾部开始向前清理的
    for (Node t = tail; t != null && t != node; t = t.prev)
      if (t.waitStatus <= 0)
        s = t;
  }
  // 由于是从尾部清理,因此s最后的值应该是同步队列中第一个waitState<=0的节点
  // 那么unpark()操作的就是同步队列中第一个等待被唤醒的节点
  if (s != null)
    LockSupport.unpark(s.thread);
}

2 方法释放锁的流程就是:
①、先交给子类去尝试释放锁,如果成功则处理同步队列的节点
②、将同步队列中的无效节点移除,然后将队列中node之后第一个有效节点唤醒

3 方法:
调用了while循环,条件是!isOnSyncQueue(node),是否不在同步队列中? 如果不在,将会执行下面的内容:

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null) 
        return false; 
        // 以节点状态作为判断条件,如果等于CONDITION(说明在等待队列中)、或者前置节点为空,是一个独立节点
    if (node.next != null) // If has successor, it must be on queue
        return true; // 如果后继节点不为空,说明它还在同步队列中。
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     * 前置节点为空,并不代表节点不在队列上,因为 CAS操作有可能失败。 因此需要从尾部遍历队列来保证它不在队列上。     
     */
    return findNodeFromTail(node); // 从尾部找到node节点
}
------------------------------------------------------------------------------------------------------
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

那么,如果while (!isOnSyncQueue(node)) 成立,就是节点node不在同步队列上,则说明node已经释放锁了,并且进入了等待队列。接下来让线程挂起、等待被唤醒就可以了

    while (!isOnSyncQueue(node)) {				3
        LockSupport.park(this); // 线程将在这里被挂起,停止运行

        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }

checkInterruptWhileWaiting(node) 执行的条件是线程被唤醒,唤醒后首先要检查的是,在这期间线程是否有被中断,保证线程安全。

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}
final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { // 将节点状态由CONDITION调整为0
        enq(node); // 加入同步队列 
        return true; 
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     * 如果忘记调用signal,那么就不能继续执行了,要让它回到同步队列中。
     */
    while (!isOnSyncQueue(node)) // 判断线程是否在同步队列,直到回到同步队列(取消,也要先让node回到同步队列)
        Thread.yield();  // 让出CPU时间
    return false; // 修改node状态失败,返回false
}

checkInterruptWhileWaiting(Node node)逻辑: 如果线程为中断状态,则进入transferAfterCancelledWait() ,里面会操作node状态由CONDITION回到初始状态0,此时如果操作成功,会将node重新放回同步队列。如果CAS失败,则需要向下执行,有可能是其他操作改变了node状态,或许是取消的场景,因为这里进入的前提是线程已经被中断。

在结束了transferAfterCancelledWait()方法后,根据返回的true/false,确定 返回THROW_IE还是REINTERRUPT状态,如果没有中断则返回0,也就是interruptMode的初始值。

总之: if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) 的逻辑 :线程被唤醒后,检查线程状态,如果是中断状态,要尝试将node的节点状态变更为0,如果变更成功,则判定中断原因是异常,如果变更失败,要给线程时间让其他线程将node放回同步队列。如果返回的不是初始值,则外层while会被break;如果是初始值,则会判断是否进入同步队列,是则结束循环,否则说明还在等待队列,需要继续被挂起。

4 当循环结束,后续流程就需要 让线程重新进入锁竞争状态,并且前面判断了那么多线程状态,也要根据返回值处理一下:

if (acquireQueued(node, savedState) && interruptMode != THROW_IE) // 4.1
	interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
	unlinkCancelledWaiters(); // 4.2
if (interruptMode != 0)
	reportInterruptAfterWait(interruptMode); // 4.3

在 4.1 位置,让节点线程再次去申请锁,同时传入挂起前保存的资源值saveState,节点回到竞争状态后就是AQS申请逻辑,可以交给AQS了;对于await()来说,剩下的就是处理线程状态了。
如果interruptMode != 异常,则调整interruptMode的值为REINTERRUPT。也就是说,如果线程申请锁成功,未来会让线程中断。
在 4.2 位置,如果节点node有后继节点,那么需要将node从等待队列移除
在 4.3 位置,如果interruptMode的值不为0,也就是不正常状态,进入reportInterruptAfterWait()方法。

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE) // 如果为异常状态
        throw new InterruptedException(); // 抛出异常
    else if (interruptMode == REINTERRUPT) // 如果为中断状态
        selfInterrupt(); // 设置线程中断
}
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
  • await()方法逻辑
    • 1、将持有锁的线程包装为node,并放入等待队列
    • 2、将持有的锁释放,保持持有锁时申请的资源值
    • 3、循环判断节点node是否在同步队列中,如果没有则挂起线程
    • 4、线程被唤醒后,要判断线程状态
    • 5、让线程去申请锁,根据申请规则,如果申请失败会在同步队列挂起
    • 6、如果申请成功,要根据线程状态对线程进行合理的处理:抛异常或中断

JDK15唤醒signal()操作:


		 public final void signal() {
		     ConditionNode first = firstWaiter;			//获取等待队列的第一个结点🧡
		     if (!isHeldExclusively())					//判断是否持有独占锁,如果不是抛出异常🧡
		        throw new IllegalMonitorStateException();
		     if (first != null)							//唤醒等待队列第一个结点的线程
		        doSignal(first, false);
		   }
🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡  🧡🧡🧡🧡

        private void doSignal(ConditionNode first, boolean all) {   //all传入的false,用来判断是不是signalAll🧡       
            while (first != null) {
                ConditionNode next = first.nextWaiter;	
           //移除条件等待队列中的第一个结点如果后继结点为null,那么说明没有其他结点,因此将尾结点也设置为null🧡	
                if ((firstWaiter = next) == null)
                    lastWaiter = null;
                if ((first.getAndUnsetStatus(COND) & COND) != 0) {  //判断该节点是不是条件状态,是则进入同步入队
                    enqueue(first);
                    if (!all)			//看用不用循环判断
                        break;
                }
                first = next;							//循环找一个
            }
        }
🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡 🧡🧡🧡🧡  🧡🧡🧡🧡
       final int getAndUnsetStatus(int v) {     // for signalling       //这里我不太会分析,但是确实比JDK8要优化了,底层用的
          return U.getAndBitwiseAndInt(this, STATUS, ~v);				//Unsafe类
       }
       
          @ForceInline
	    public final int getAndBitwiseAndInt(Object o, long offset, int mask) {
	        int current;
	        do {
	            current = getIntVolatile(o, offset);
	        } while (!weakCompareAndSetInt(o, offset,
	                                                current, current & mask));
	        return current;
	    }
  • 总结:
    • 这里signal()方法做了两件事:
      • 一是判断等待队列的第一个结点中的当前线程是否持有独占锁,没有就抛出异常,从这点也可以看出只有独占模式先采用等待队列,而共享模式下是没有等待队列的,也就没法使用Condition
      • 二是唤醒等待队列的第一个结点(前提不为空,也就是先await(),再signal()),即执行doSignal(first, false)
    • doSignal(ConditionNode first, boolean all)方法显示了在JDK8上的 doSignal(first)的优化:
      • 通过参数all来判断是signal()还是signalAll()
      • 将状态为conditon的,全都放入到同步队列中(这里是优化的代码)
  • 流程如下图所示(注意无论是同步队列还是等待队列使用的Node数据结构都是同一个,不过是使用的内部变量不同罢了)
    在这里插入图片描述
  • 差不多就到这里吧,基于AQS的共享锁以后分析,还准备分析下线程池,以及一些并发的数据结构。
  • 说是分析,其实就是学习的过程,谢谢一些优质博客的大佬!!!加油!
  • await()和进队操作不能准确把握。
  • 对于自旋锁和是否挂起在信号量Semaphore的文章中有所总结。点击!!!!!
    💛🧡🧡🧡🧡🧡 💛🧡🧡🧡🧡🧡 💛🧡🧡🧡🧡🧡 💛🧡🧡🧡🧡🧡
  • 到现在,看到多线程中,锁定的方式有2种:synchronized和ReentrantLock。两种锁定方式各有优劣,下面简单对比一下:
  • synchronized和ReentrantLock的对比:
  • synchronized是关键字,就和if…else…一样,是语法层面的实现,因此synchronized获取锁以及释放锁都是Java虚拟机帮助用户完成的;ReentrantLock是类层面的实现,因此锁的获取以及锁的释放都需要用户自己去操作。特别再次提醒,ReentrantLock在lock()完了,一定要手动unlock()
  • synchronized简单,简单意味着不灵活,而ReentrantLock的锁机制给用户的使用提供了极大的灵活性。这点在Hashtable和ConcurrentHashMap中体现得淋漓尽致。 synchronized一锁就锁整个Hash表,而ConcurrentHashMap则利用ReentrantLock实现了锁分离,锁的只是segment而不是整个Hash表
  • synchronized是不公平锁,而ReentrantLock可以指定锁是公平的还是非公平的
  • synchronized实现等待/通知机制通知的线程是随机的,ReentrantLock实现等待/通知机制可以有选择性地通知
  • 和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,比如可以知道lock是否被当前线程获取、lock被同一个线程调用了几次、lock是否被任意线程获取等等
  • 总结起来,我认为如果只需要锁定简单的方法、简单的代码块,那么考虑使用synchronized,复杂的多线程处理场景下可以考虑使用ReentrantLock。当然这只是建议性地,还是要具体场景具体分析的。
  • 最后,查看了很多资料,JDK1.5版本由于对synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。
  • 14
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值