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

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

  • 大厂之路一由浅入深、并行基础、源码分析一synchronized关键字,以及相应优化!!
  • 大厂之路一由浅入深、并行基础、源码分析一Unsafe类、varHandle类、无锁CAS、及其并发包Atomic
  • 大厂之路一由浅入深、并行基础、源码分析一 “JUC锁”之框架 以及 LOCK锁较synchronized的改进
  • 大厂之路一由浅入深、并行基础、源码分析一 “JUC锁”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的应用

  • 文章中JDK8部分的内容总结自:点击如下!!!!!!
  • 本篇文章中的一些伪代码可以更好的理解ReentrantLock的实现方式 点击如下!!!!!!
  • 没有参考此文章,但是总结的好,等以后复习可以看看点击如下!!!!!!
  • 没有参考此文章,等以后复习可以看看点击如下!!!!!!
  • 可以看一看,前提能力够JDK14的源码!!!!

  • Lock接口
    • 重入锁ReentrantLock
  • 并发基础组件AQS与ReetrantLock
    • AQS的同步队列模型
      • 不同JDK的Node节点源码分析:
    • 基于ReetrantLock分析AQS独占锁模式
    • ReentrantLock之非公平锁的操作实现过程:
      • JDK15版本(没分析完):
      • JDK8版本(基于大神的blog进行分析):
      • lockInterruptibly()/tryLock()方法和lock()的源码级对比:
    • ReentrantLock之公平锁的操作实现过程:
      • FairSync和NonfairSync的最大区别:!!!!
    • ReentrantLock(公平锁、非公平锁)释放锁的操作实现过程:
    • Condition接口及原理
      • JDK15的await()方法
      • JDK8的await()方法
      • JDK15唤醒signal()操作:


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

  • AQS的工作原理:
    • AbstractQueuedSynchronizer 称为队列同步器(简称AQS);
    • 它是用来构建锁或其他同步组件的基础框架
    • state变量: 内部通过一个int类型的成员变量state来控制同步状态private volatile int state;
      • 当state=0时,没有任何线程占有共享资源的锁,
      • 当state>=1时,有线程正占用锁,其他线程必须加入同步队列进行等待;
    • 同步队列
      • AQS内部通过内部类Node构成FIFO的同步队列abstract static class Node { ...}来完成线程获取锁的排队工作;
    • 等待队列
      • 利用内部类ConditionObject构建等待队列
      • 当Condition调用await()方法后,线程将会加入等待队列中,
      • 当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行同步状态state(也就是锁)竞争。
    • 两种队列的使用:
      • 当线程请求锁而等待,将加入同步队列等待;
      • 通过Condition调用await()方法释放锁后,将加入等待队列(可有多个)
  • AQS的内部类:ExclusiveNode、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修饰
	
	//其他代码...

同步队列的模型:
大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第1张图片

  • 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 4volatile 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的构造: 大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第2张图片

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

      • CountDownLatch、ThreadPoolExecutor(后边文章介绍);
      • ReentrantLock(独占锁、本文章介绍)
      • ReentrantReadWriteLock(读共享、写独占后边文章介绍)
      • Semaphore(共享锁、后边文章介绍):
      • 注意:ReentrantLock、ReentrantReadWriteLock、Semaphore都有对应的公平锁、非公平锁
    • ReentrantLock为例,简单讲解ReentrantLock(独占锁)与AQS的关系 : 大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第3张图片

      • 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独占锁模式

  • 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))方法,这两个方法体现了公平/非公平锁加锁的不同。我们通过下图先简单看一下:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第4张图片
  • 通过上图我们可以知道,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;
        }
    }
  • 这里获取锁时,首先对同步状态执行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作为队首,然后重新入队;
  • 发现:在前面的请求同步状态有两次机会,以及请求同步状态失败后我们将结点添加到尾部的操作也有两次机会(虽然第二次机会一定会添加到同步队列尾部)!!!
  • 以上就是一个入队前的操作,那当入队后做什么呢(自旋!!)?别急,我们慢慢看!!!
  • 我们再次看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) {
     	 
    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结点释放同步状态后唤醒后继结点,
      • 执行结果如下图(别人的图、地址在文章头):
        大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第5张图片
  • 从图可知:

    • 更新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方法。

  • 到此ReetrantLock内部间接通过AQS的FIFO的同步队列就完成了lock()操作,这里我们总结成逻辑流程图:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第6张图片
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第7张图片

  • 以上基于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规则,也就是说先到先得,不能插队,接下来我们通过类结构进一步分析:
  • 大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第8张图片
  • 通过上图我们可以发现,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);				设置Owner为null,也就是释放锁了(不让线程持有锁了)
          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);
        }
    }
                 
     LockjSupport的unpark(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接口及原理

  • 在并发编程中,每个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。
    • 大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第9张图片
  • 我们具体来看一下这个内部类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() {
      }		 //空构造器
        //.....其他代码
  • 在实现类ConditionObject中有两个结点分别是firstWaiterlastWaiter
    • firstWaiter: 代表等待队列第一个等待结点,
    • lastWaiter: 代表等待队列最后一个等待结点;
  • 每个Condition都对应着一个等待队列,也就是说如果:
    • 一个锁上创建了多个Condition对象,那么也就存在多个等待队列
  • 等待队列是一个FIFO的队列,在队列中每一个节点都包含了一个线程的引用,而该线程就是Condition对象上等待的线程。
  • 当一个线程调用了await() 相关的方法,那么该线程将会释放锁,并构建一个Node节点封装当前线程的相关信息加入到等待队列中进行等待,直到被唤醒、中断、超时才从队列中移出。
  • 看一下ConditionNode类中的属性:
    • ConditionNode nextWaiter; // link to next waiting node 说明是单向链表
  • Condition中的等待队列模型:
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第10张图片
  • 如图所示:
    • 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 {
     
		//首先判断线程是否被中断,中断则直接抛出异常
      if (Thread.interrupted())			
          throw new InterruptedException();
          
   		//创建新结点加入等待队列
      Node node = addConditionWaiter();		
       //释放当前结点的锁即释放同步状态
      int savedState = fullyRelease(node);	
      int interruptMode = 0;
   	  //判断结点是否在同步队列(SyncQueue)中,即是否被唤醒
      while (!isOnSyncQueue(node)) {
     	
       	  //如果不在同步队列中,则挂起线程(在等待队列中挂起)
          LockSupport.park(this);		
          //判断是否被中断唤醒,如果是退出循环。
          if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)   
              break;
      }
       //被唤醒后执行(因此唤醒后会由等待队列转移到同步队列)自旋操作争取获得锁,同时判断线程是否被中断
      if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 
          interruptMode = REINTERRUPT;
       // clean up if cancelled
      if (node.nextWaiter != null) 
          //清理等待队列中不为CONDITION状态的结点
          unlinkCancelledWaiters();
      if (interruptMode != 0)
          reportInterruptAfterWait(interruptMode);
  }
           
// 执行addConditionWaiter() , 添加到等待队列。  
	private Node addConditionWaiter() {
     
	    Node t = lastWaiter;		// t是等待队列的尾节点
		   // 判断是否为结束状态的结点并移除,也就是将t设置为非结束状态的尾部节点
	      if (t != null && t.waitStatus != Node.CONDITION) {
           
	          unlinkCancelledWaiters();
	          t = lastWaiter;
	      }
	     //创建新结点状态,并设置为CONDITION状态
	      Node node = new Node(Thread.currentThread(), Node.CONDITION);
		//加入等待队列,如果等待队列为空,则将当前节点设为第一个节点,否则作为新的尾节点
	      if (t == null) 
	          firstWaiter = node;
	      else
	          t.nextWaiter = node;
	      lastWaiter = node;
	      return node;  //返回等待队列尾部节点(前一个结点不是结束状态)
	        }
  • await()方法主要做了3件事:
    • 一是调用addConditionWaiter()方法:将当前线程封装成node结点加入到等待队列;
    • 二是调用fullyRelease(node)方法:释放同步状态唤醒后继(同步队列的)结点的线程;
    • 三是调用isOnSyncQueue(node)方法:判断结点是否在同步队列中,注意是个while循环;
      • 如果同步队列中没有该结点就直接挂起该线程
      • 如果线程被唤醒后就调用acquireQueued(node, savedState)执行自旋操作争取锁(当前线程结点从等待队列转移到同步队列并开始努力获取锁)。

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数据结构都是同一个,不过是使用的内部变量不同罢了)
    大厂之路一由浅入深、并行基础、源码分析一 “J.U.C.L”之重入锁(ReetrantLock)、公平锁、非公平锁及Condition的源码级分析(基于AQS、独占锁)(JDK不同版本对比!!)_第11张图片
  • 差不多就到这里吧,基于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应该是差不多。

你可能感兴趣的:(J.U.C,源码)