走进源码学习Lock和AQS(AbstractQueuedSynchronizer)怎么配合实现加锁流程

JUC:java.util.concurrent的缩写,Java的并发编程类都在这个包下面。
AQS:AbstractQueuedSynchronizer抽象类的缩写,ReentrantLock、Condition、ReentrantReadWriteLock、CountDownLatch、Semaphore、等这些我们常用的类都跟它有关。



日常使用ReentrantLock锁的示例

ReentrantLock类提供了无参和有参两种构造方法,无参构造方法等价于有参构造方法时传false值,创建非公平锁。

Lock lock = new ReentrantLock();
lock.lock();
try {
    /*
     * 业务代码...
     */
} catch (Exception e) {
    log.error("", e);
} finally {
    lock.unlock();
}

创建公平锁,传参true。

Lock lock = new ReentrantLock(true);
lock.lock();
try {
    /*
     * 业务代码...
     */
} catch (Exception e) {
    log.error("", e);
} finally {
    lock.unlock();
}

公平锁、非公平锁使用上一样,lock()方法手动开启锁,使用完后unlock()方法手动释放锁。切记lock.unlock()放到finally里。


主要类的结构

Lock接口

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    Condition newCondition();
}

AbstractOwnableSynchronizer

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    // 独占锁的线程
	private transient Thread exclusiveOwnerThread;
}

AQS (AbstractQueuedSynchronizer)

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	/**
     * 队列头
     */
    private transient volatile Node head;
	/**
     * 队列尾
     */
    private transient volatile Node tail;

    /**
     * 锁状态:默认0未加锁,加锁成功1,重入+1
     */
    private volatile int state;
    
    /**
     * Node节点类,双向链表类,里包含了上一个节点、下一个节点和当前线程,
     * 某种意义上Node就等于一个线程。
     */
	static final class Node {
		volatile int waitStatus;
		volatile Node prev;
		volatile Node next;
		volatile Thread thread;
		Node nextWaiter;
    }

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

ReentrantLock

public class ReentrantLock implements Lock, java.io.Serializable {

	/**
	 * 包含具体实现的抽象同步器
	 */
    private final Sync sync;

	/**
	 * Sync继承了AQS
	 */
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
    	abstract void lock();
    }
    
    /**
     * 非公平锁的同步器
     * NonfairSync类继承了Sync
     */
	static final class NonfairSync extends Sync {
	
		final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
	}
	
    /**
     * 公平锁的同步器
     * FairSync类继承了Sync
     */
	static final class FairSync extends Sync {
	
		final void lock() {
            acquire(1);
        }
	}
	
	/**
	 * 无参构造方法
	 */
	public ReentrantLock() {
        sync = new NonfairSync();
    }

	/**
	 * 有参构造方法,fair默认为false
	 */
	public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

	public void lock() {
        sync.lock();
    }
    
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    
	public void unlock() {
        sync.release(1);
    }
    
    public Condition newCondition() {
        return sync.newCondition();
    }
    

ReentrantLock.Sync的继承关系:


AQS中的队列关系图

在这里插入图片描述


公平锁和非公平锁源码上的差异

lock加锁的差异

// 公平锁的lock()
final void lock() {
   acquire(1);
}

// 非公平锁的lock()
final void lock() {
   if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
   else
       acquire(1);
}

acquire()里tryAcquire()的差异

// 公平锁的tryAcquire()
protected final boolean tryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
       if (!hasQueuedPredecessors() &&
           compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if (nextc < 0)
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

// 非公平锁的tryAcquire()
final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) {
       if (compareAndSetState(0, acquires)) {
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if (nextc < 0) // overflow
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

从加锁流程看源码

了解完主要类的结构后,我们从加锁过程来看看底层源码是怎么实现的。
以公平锁获取锁为例,大概的几种情况:

  • t1来,直接加锁成功(不会初始化队列);
  • t2来,如果t1已执行完释放锁,t2的流程跟t1一样;
  • t2来,如果t1还正在执行,t2会初始化队列,看看t1是不是队列头,是的话会再次尝试拿锁,如果拿到把自己设置为队头(并解绑自己和上一任队头),如果没拿到把ws设置为-1然后自旋再次尝试拿锁,两次都拿不到就park;如果不是队列头直接park。
  • t3来,如果此时t1还在执行,t3会会加入队列,看看前辈不是队列头(是正在park的t2),t3则直接park。

lock()

final void lock() {
   // 加锁成功之后修改AQS的state的值为1
   acquire(1);
}

acquire()

AQS的acquire()方法:

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

首先调用tryAcquire()方法尝试加锁,注意tryAcquire()的结果做了取反,如果加锁失败则会调用acquireQueued()方法;如果加锁成功,则此流程结束。

tryAcquire()尝试加锁

/**
 * 如果c等于0,加锁成功,返回true
 * 如果c不等于0,而且当前线程不等于拥有锁的线程则直接返回false,加锁失败
 * 如果c不等于0,但是当前线程等于拥有锁的线程则表示这是一次重入,那么直接把状态+1表示重入次数+1。那么这里也侧面说明了reentrantlock是可以重入的,因为如果是重入也返回true,也能lock成功
*/
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //如果在当前线程之前有一个排队线程,则为true;如果当前线程位于队列的头部或队列为空,则为false
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置当前线程为拥有锁的线程,方便后面判断是不是重入
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 重入锁+1
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

hasQueuedPredecessors()判断自己是否需要排队

/**
 * 如果在当前线程之前有一个排队线程,则为true;
 * 如果当前线程位于队列的头部或队列为空(t、h都是null,没有引用),则为false
 */
public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    /**
     * 不需要排队有三种情况
     * 一:队列没有初始化,不需要排队。
     * 直接去加锁,但是可能会失败;为什么会失败呢?
     * 假设两个线程同时来lock,都看到队列没有初始化,都认为不需要排队,都去进行CAS修改计数器;有一个必然失败
     * 比如t1先拿到锁,那么另外一个t2则会CAS失败,这个时候t2就会去初始化队列acquireQueued(),并排队
     *
     * 二:队列被初始化了,但是tc过来加锁,发觉队列当中第一个排队的就是自己;比如重入;
     * 
     * 三:持有的锁正好被释放,自己是队列中第一个排队的,正在排队时会先去尝试获取一下锁,因为有可能这个时候持有锁锁的那个线程可能释放了锁;
     * 如果释放了就直接获取锁执行。但是如果没有释放他就会去排队,
     * 所以这里的不需要排队,不是真的不需要排队
     *
     * h != t 判断首不等于尾这里要分三种情况
     * 1、队列没有初始化,也就是第一个线程tf来加锁的时候那么这个时候队列没有初始化,
     * h和t都是null,那么这个时候判断不等于则不成立(false)那么由于是&&运算后面的就不会走了,
     * 直接返回false表示不需要排队,而前面又是取反(if (!hasQueuedPredecessors()),所以会直接去cas加锁。
     *
     * 2、队列被初始化了,后面会分析队列初始化的流程,如果队列被初始化那么h!=t则成立;(不绝对,还有第3中情况)
     * 大于1个数据则成立;继续判断把h.next赋值给s;s有是对头的下一个Node,
     * 这个时候s则表示他是队列当中参与排队的线程而且是排在最前面的;
     * 为什么是s最前面不是h嘛?诚然h是队列里面的第一个,但是不是排队的第一个;下文有详细解释
     * 因为h也就是对头对应的Node对象或者线程他是持有锁的,但是不参与排队;
     * 这个很好理解,比如你去买车票,你如果是第一个这个时候售票员已经在给你服务了,你不算排队,你后面的才算排队;
     * 队列里面的h是不参与排队的这点一定要明白;参考下面关于队列初始化的解释;
     * 因为h要么是虚拟出来的节点,要么是持有锁的节点;什么时候是虚拟的呢?什么时候是持有锁的节点呢?下文分析
     * 然后判断s是否等于空,其实就是判断队列里面是否只有一个数据;
     * 假设队列大于1个,那么肯定不成立(s==null---->false),因为大于一个Node的时候h.next肯定不为空;
     * 由于是||运算如果返回false,还要判断s.thread != Thread.currentThread();这里又分为两种情况
     *        2.1 s.thread != Thread.currentThread() 返回true,就是当前线程不等于在排队的第一个线程s;
     *              那么这个时候整体结果就是h!=t:true; (s==null false || s.thread != Thread.currentThread() true  最后true)
     *              结果: true && true 方法最终放回true,所以需要去排队
     *              其实这样符合情理,试想一下买火车票,队列不为空,有人在排队;
     *              而且第一个排队的人和现在来参与竞争的人不是同一个,那么你就乖乖去排队
     *        2.2 s.thread != Thread.currentThread() 返回false 表示当前来参与竞争锁的线程和第一个排队的线程是同一个线程
     *             这个时候整体结果就是h!=t---->true; (s==null false || s.thread != Thread.currentThread() false-----> 最后false)
     *            结果:true && false 方法最终放回false,所以不需要去排队
     *            不需要排队则调用 compareAndSetState(0, acquires) 去改变计数器尝试上锁;
     *            这里又分为两种情况(日了狗了这一行代码;有同学课后反应说子路老师老师老是说这个AQS难,
     *            你现在仔细看看这一行代码的意义,真的不简单的)
     *             2.2.1  第一种情况加锁成功?有人会问为什么会成功啊,如这个时候h也就是持有锁的那个线程执行完了
     *                      释放锁了,那么肯定成功啊;成功则执行 setExclusiveOwnerThread(current); 然后返回true 自己看代码
     *             2.2.2  第二种情况加锁失败?有人会问为什么会失败啊。假如这个时候h也就是持有锁的那个线程没执行完
     *                       没释放锁,那么肯定失败啊;失败则直接返回false,不会进else if(else if是相对于 if (c == 0)的)
     *                      那么如果失败怎么办呢?后面分析;
     *
     *----------第二种情况总结,如果队列被初始化了,而且至少有一个人在排队那么自己也去排队;但是有个插曲;
     * ----------他会去看看那个第一个排队的人是不是自己,如果是自己那么他就去尝试加锁;尝试看看锁有没有释放
     *----------也合情合理,好比你去买票,如果有人排队,那么你乖乖排队,但是你会去看第一个排队的人是不是你女朋友;
     *----------如果是你女朋友就相当于是你自己(这里实在想不出现实世界关于重入的例子,只能用男女朋友来替代);
     * --------- 你就叫你女朋友看看售票员有没有搞完,有没有轮到你女朋友,因为你女朋友是第一个排队的
     * 疑问:比如如果在在排队,那么他是park状态,如果是park状态,自己怎么还可能重入啊。
     * 希望有同学可以想出来为什么和我讨论一下,作为一个菜逼,希望有人教教我
     *  
     * 
     * 3、队列被初始化了,但是里面只有一个数据;什么情况下才会出现这种情况呢?ts加锁的时候里面就只有一个数据?
     * 其实不是,因为队列初始化的时候会虚拟一个h作为头结点,tc=ts作为第一个排队的节点;tf为持有锁的节点
     * 为什么这么做呢?因为AQS认为h永远是不排队的,假设你不虚拟节点出来那么ts就是h,
     *  而ts其实需要排队的,因为这个时候tf可能没有执行完,还持有着锁,ts得不到锁,故而他需要排队;
     * 那么为什么要虚拟为什么ts不直接排在tf之后呢,上面已经时说明白了,tf来上锁的时候队列都没有,他不进队列,
     * 故而ts无法排在tf之后,只能虚拟一个thread=null的节点出来(Node对象当中的thread为null);
     * 那么问题来了;究竟什么时候会出现队列当中只有一个数据呢?假设原队列里面有5个人在排队,当前面4个都执行完了
     * 轮到第五个线程得到锁的时候;他会把自己设置成为头部,而尾部又没有,故而队列当中只有一个h就是第五个
     * 至于为什么需要把自己设置成头部;其实已经解释了,因为这个时候五个线程已经不排队了,他拿到锁了;
     * 所以他不参与排队,故而需要设置成为h;即头部;所以这个时间内,队列当中只有一个节点
     * 关于加锁成功后把自己设置成为头部的源码,后面会解析到;继续第三种情况的代码分析
     * 记得这个时候队列已经初始化了,但是只有一个数据,并且这个数据所代表的线程是持有锁
     * h != t false 由于后面是&&运算,故而返回false可以不参与运算,整个方法返回false;不需要排队
     *
     *
     *-------------第三种情况总结:如果队列当中只有一个节点,而这种情况我们分析了,
     *-------------这个节点就是当前持有锁的那个节点,故而我不需要排队,进行cas;尝试加锁
     *-------------这是AQS的设计原理,他会判断你入队之前,队列里面有没有人排队;
     *-------------有没有人排队分两种情况;队列没有初始化,不需要排队
     *--------------队列初始化了,按时只有一个节点,也是没人排队,自己先也不排队
     *--------------只要认定自己不需要排队,则先尝试加锁;加锁失败之后再排队;
     *--------------再一次解释了不需要排队这个词的歧义性
     *-------------如果加锁失败了,在去park,下文有详细解释这样设计源码和原因
     *-------------如果持有锁的线程释放了锁,那么我能成功上锁
     *
     **/
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}


参考此图理解:

到此我们已经解释完了!tryAcquire(arg)方法。如果尝试拿锁失败,接下来开始执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。有两种情况:
1、tf持有了锁,并没有释放,所以tc来加锁的时候需要排队,但这个时候—队列并没有初始化
2、tn(无所谓哪个线程,反正就是一个线程)持有了锁,那么由于加锁tn!=tf(tf是属于第一种情况,我们现在不考虑tf了),所以队列是一定被初始化了的,tc来加锁,那么队列当中有人在排队,故而他也去排队。

addWaiter(Node.EXCLUSIVE)

private Node addWaiter(Node mode) {
    //由于AQS队列当中的元素类型为Node,故而需要把当前线程tc封装成为一个Node对象,下文我们叫做nc
    Node node = new Node(Thread.currentThread(), mode);
    //tail为对尾,赋值给pred 
    Node pred = tail;
    //判断pred是否为空,其实就是判断对尾是否有节点,其实只要队列被初始化了对尾肯定不为空,
    //假设队列里面只有一个元素,那么对尾和对首都是这个元素
    //换言之就是判断队列有没有初始化
    //上面我们说过代码执行到这里有两种情况,1、队列没有初始化和2、队列已经初始化了
    //pred不等于空表示第二种情况,队列被初始化了,如果是第二种情况那比较简单
   //直接把当前线程封装的nc的上一个节点设置成为pred即原来的对尾
   //继而把pred的下一个节点设置为当nc,这个nc自己成为对尾了
    if (pred != null) {
        //直接把当前线程封装的nc的上一个节点设置成为pred即原来的对尾,对应 10行的注释
        node.prev = pred;
        //这里需要cas,因为防止多个线程加锁,确保nc入队的时候是原子操作
        if (compareAndSetTail(pred, node)) {
            //继而把pred的下一个节点设置为当nc,这个nc自己成为对尾了 对应第11行注释
            pred.next = node;
            //然后把nc返回出去,方法结束
            return node;
        }
    }
    //如果上面的if不成了就会执行到这里,表示第一种情况队列并没有初始化---下面解析这个方法
    enq(node);
    //返回nc
    return node;
}


private Node enq(final Node node) {//这里的node就是当前线程封装的node也就是nc
    //死循环
    for (;;) {
        //对尾复制给t,上面已经说过队列没有初始化,
        //故而第一次循环t==null(因为是死循环,因此强调第一次,后面可能还有第二次、第三次,每次t的情况肯定不同)
        Node t = tail;
        //第一次循环成了成立
        if (t == null) { // Must initialize
            //new Node就是实例化一个Node对象下文我们称为nn,
            //调用无参构造方法实例化出来的Node里面三个属性都为null,可以关联Node类的结构,
            //compareAndSetHead入队操作;把这个nn设置成为队列当中的头部,cas防止多线程、确保原子操作;
            //记住这个时候队列当中只有一个,即nn
            if (compareAndSetHead(new Node()))
                //这个时候AQS队列当中只有一个元素,即头部=nn,所以为了确保队列的完整,设置头部等于尾部,即nn即是头也是尾
                //然后第一次循环结束;接着执行第二次循环,第二次循环代码我写在了下面,接着往下看就行
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}


//为了方便 第二次循环我再贴一次代码来对第二遍循环解释
private Node enq(final Node node) {//这里的node就是当前线程封装的node也就是nc
    //死循环
    for (;;) {
        //对尾复制给t,由于第二次循环,故而tail==nn,即new出来的那个node
        Node t = tail;
        //第二次循环不成立
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //不成立故而进入else
            //首先把nc,当前线程所代表的的node的上一个节点改变为nn,因为这个时候nc需要入队,入队的时候需要把关系维护好
            //所谓的维护关系就是形成链表,nc的上一个节点只能为nn,这个很好理解
            node.prev = t;
            //入队操作--把nc设置为对尾,对首是nn,
            if (compareAndSetTail(t, node)) {
                //上面我们说了为了维护关系把nc的上一个节点设置为nn
                //这里同样为了维护关系,把nn的下一个节点设置为nc
                t.next = node;
                //然后返回t,即nn,死循环结束,enq(node);方法返回
                //这个返回其实就是为了终止循环,返回出去的t,没有意义
                return t;
            }
        }
    }
}

  //这个方法已经解释完成了
  enq(node);
  //返回nc,不管哪种情况都会返回nc;到此addWaiter方法解释完成
  return node;


//再次贴出node的结构方便大家查看
public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
}



-------------------总结:addWaiter方法就是让nc入队-并且维护队列的链表关系,但是由于情况复杂做了不同处理
-------------------主要针对队列是否有初始化,没有初始化则new一个新的Node nn作为对首,nn里面的线程为null
-------------------接下来分析acquireQueued方法

acquireQueued(addWaiter(Node.exclusive),arg))经过上面的解析之后可以理解成为acquireQueued(nc,arg))。

acquireQueued()

final boolean acquireQueued(final Node node, int arg) {//这里的node 就是当前线程封装的那个node 下文叫做nc
    //记住标志很重要
    boolean failed = true;
    try {
        //同样是一个标志
        boolean interrupted = false;
        //死循环
        for (;;) {
            //获取nc的上一个节点,有两种情况;1、上一个节点为头部;2上一个节点不为头部
            final Node p = node.predecessor();
            //如果nc的上一个节点为头部,则表示nc为队列当中的第二个元素,为队列当中的第一个排队的Node;
            //这里的第一和第二不冲突;我上文有解释;
            //如果nc为队列当中的第二个元素,第一个排队的则调用tryAcquire去尝试加锁---关于tryAcquire看上面的分析
            //只有nc为第二个元素;第一个排队的情况下才会尝试加锁,其他情况直接去park了,
            //因为第一个排队的执行到这里的时候需要看看持有有锁的线程有没有释放锁,释放了就轮到我了,就不park了
            //有人会疑惑说开始调用tryAcquire加锁失败了(需要排队),这里为什么还要进行tryAcquire不是重复了吗?
            //其实不然,因为第一次tryAcquire判断是否需要排队,如果需要排队,那么我就入队;
            //当我入队之后我发觉前面那个人就是第一个,持有锁的那个,那么我不死心,再次问问前面那个人搞完没有
            //如果他搞完了,我就不park,接着他搞我自己的事;如果他没有搞完,那么我则在队列当中去park,等待别人叫我
            //但是如果我去排队,发觉前面那个人在睡觉,前面那个人都在睡觉,那么我也睡觉把---------------好好理解一下
            if (p == head && tryAcquire(arg)) {
                //能够执行到这里表示我来加锁的时候,锁被持有了,我去排队,进到队列当中的时候发觉我前面那个人没有park,
                //前面那个人就是当前持有锁的那个人,那么我问问他搞完没有
                //能够进到这个里面就表示前面那个人搞完了;所以这里能执行到的几率比较小;但是在高并发的世界中这种情况真的需要考虑
                //如果我前面那个人搞完了,我nc得到锁了,那么前面那个人直接出队列,我自己则是对首;这行代码就是设置自己为对首
                setHead(node);
                //这里的P代表的就是刚刚搞完事的那个人,由于他的事情搞完了,要出队;怎么出队?把链表关系删除
                p.next = null; // help GC
                //设置表示---记住记加锁成功的时候为false
                failed = false;
                //返回false;为什么返回false?下次博客解释---比较复杂和加锁无关
                return interrupted;
            }
            //进到这里分为两种情况
            //1、nc的上一个节点不是头部,说白了,就是我去排队了,但是我上一个人不是队列第一个
            //2、第二种情况,我去排队了,发觉上一个节点是第一个,但是他还在搞事没有释放锁
            //不管哪种情况这个时候我都需要park,park之前我需要把上一个节点的状态改成park状态
            //这里比较难以理解为什么我需要去改变上一个节点的park状态呢?每个node都有一个状态,默认为0,表示无状态
            //-1表示在park;当时不能自己把自己改成-1状态?为什么呢?因为你得确定你自己park了才是能改为-1;
            //不然你自己改成自己为-1;但是改完之后你没有park那不就骗人?
            //你对外宣布自己是单身状态,但是实际和刘宏斌私下约会;这有点坑人
            //所以只能先park;在改状态;但是问题你自己都park了;完全释放CPU资源了,故而没有办法执行任何代码了,
            //所以只能别人来改;故而可以看到每次都是自己的后一个节点把自己改成-1状态
            //关于shouldParkAfterFailedAcquire这个方法的源码下次博客继续讲吧
            if (shouldParkAfterFailedAcquire(p, node) &&
                //改上一个节点的状态成功之后;自己park;到此加锁过程说完了
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

加锁过程总结:
如果是第一个线程tf,那么和队列无关,线程直接持有锁。并且也不会初始化队列,如果接下来的线程都是交替执行,那么永远和AQS队列无关,都是直接线程持有锁,如果发生了竞争,比如tf持有锁的过程中T2来lock,那么这个时候就会初始化AQS,初始化AQS的时候会在队列的头部虚拟一个Thread为NULL的Node,因为队列当中的head永远是持有锁的那个node(除了第一次会虚拟一个,其他时候都是持有锁的那个线程锁封装的node),现在第一次的时候持有锁的是tf而tf不在队列当中所以虚拟了一个node节点,队列当中的除了head之外的所有的node都在park,当tf释放锁之后unpark某个(基本是队列当中的第二个,为什么是第二个呢?前面说过head永远是持有锁的那个node,当有时候也不会是第二个,比如第二个被cancel之后,至于为什么会被cancel,不在我们讨论范围之内,cancel的条件很苛刻,基本不会发生)node之后,node被唤醒,假设node是t2,那么这个时候会首先把t2变成head(sethead),在sethead方法里面会把t2代表的node设置为head,并且把node的Thread设置为null,为什么需要设置null?其实原因很简单,现在t2已经拿到锁了,node就不要排队了,那么node对Thread的引用就没有意义了。所以队列的head里面的Thread永远为null

非公平锁的拿锁流程的差异:

lock()方法先抢一次锁:

)
跟公平锁比,多了红色框框的代码,即先抢一次,抢不到再执行acquire(1)走常规流程。
在这里插入图片描述

tryAcquire()不判断自己是否需要排队

公平锁的上锁是必须判断自己是不是需要排队;而非公平锁是直接进行CAS修改计数器看能不能加锁成功;如果加锁不成功则乖乖排队(调用acquire);所以不管公平还是不公平;只要进到了AQS队列当中那么他就会排队;一朝排队,永远排队。


总结:
1、如果线程是交替执行的,那么AQS队列不会初始化,减少不必要的开销;(示例一)
2、线程之间有资源竞争,但时间相隔很短,队列会初始化,拿不到锁的线程会入队,但不会阻塞,会自旋尝试拿锁,减少park、unpark;(示例二第一种情况)
3、线程之间有资源竞争,但时间相隔很长,队列会初始化,拿不到锁的线程会入队,且阻塞等待唤醒。(示例二第二种情况)


经验:
1、如果调试一遍,只能看懂代码流转流程,再多思考一点就会感觉复杂,但理清代码的意图后就会感觉好一点;
2、Node节点部分看不懂,可以自己画图结合着看。

参考文章:
https://blog.youkuaiyun.com/java_lyvee/article/details/98966684

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值