AQS源码详细分析,让你掌握AQS原理,独占锁、共享锁、Condition

这篇文章我们来说下AQS。

1、AQS简介

AQS全称为AbstractQueuedSynchronizer,是ReentrantLock、Semaphore、CountDownLatch等并发工具的基础类。

AQS与Synchronized有一个区别是:
Synchronized中所有阻塞的线程都集中到了一个队列中,当有线程释放锁的时候,会将所有线程都唤醒。因为有些线程需要的条件并不满足,但是也被唤醒了,这种线程唤醒了即使得到了锁也无法执行,这就会浪费了资源。

AQS相较于Synchronized内置了多个Condition,会针对Conditon进行精确的唤醒,提高了运行效率。

AQS的三个关键如下:
1、state
state用来标记共享变量的状态,一般用volatile来修饰。
2、queue
当线程请求锁失败后,将线程包装为一个Node,加入到queue中,等待后续的唤醒操作。
3、CAS
利用CAS来修改state和queue中的入队操作等。CAS的全称是CompareAndSwap(比较然后交换)。
CAS的一般步骤为:
1、获取当前内存中的值作为expectValue
2、CAS(offset,expectValue,updateValue)

CAS方法的参数是(offset,expectValue,updateValue),具体做法为通过offset找到内存中对应的值,如果内存中的值==expectValue的话,就将updateValue和内存中的值交换位置,一定要注意,不是单单写入新值,而是新值和旧值交换位置。

CAS有可能失败,因为其他线程已经步骤1和步骤2的中间趁机已经CAS了,将内存中写入了新值,新值于expectValue不一样,CAS会失败。

由此看来,CAS是一种乐观锁,伴随着自旋,也就是死循环for(;;)来不断CAS知道成功。

2、AQS详解

AQS有两种模式:独占模式和共享模式。

2.1、独占模式

我们先说下独占模式。独占模式下,同时只能有一个线程获得锁。

我们先说下主要的属性

state

private volatile int state;

state被volatile修饰,默认为0。state当为0的时候,说明当前锁没有被占用。state大于0的时候,说明当前锁被占用了。由于AQS是可重入的,当重入的时候state+1。

exclusiveOwnerThread

private transient Thread exclusiveOwnerThread;

对应着当前持有锁的线程对象

head和tail
head和tail代表着Queue的头指针和尾指针。

private transient volatile Node head;
private transient volatile Node tail;

Queue对应的基本类型为Node,Node指的就是一个等待的线程包装体。
我们看下Node的具体结构


//前指针
volatile Node prev;
//后指针
volatile Node next;
//对应的线程对象
volatile Thread thread;
//常量对应着共享Node
static final Node SHARED = new Node();
//常量对应着独占Node
static final Node EXCLUSIVE = null;

//于共享模式有关,暂时先不说
static final Node nextWaiter = null;

//线程的等待状态,下面四个常量就是对应的几种waitStatus
volatile int waitStatus;
//当前线程因为异常情况被取消,也就是要放弃锁了
static final int CANCELLED =  1;
//后继节点对应的线程被挂起了,当前线程释放锁或者要放弃锁的时候,需要将其唤醒或者为其找到新的唤醒者。
static final int SIGNAL    = -1;
//当前节点对应的线程等待着Condition唤醒
static final int CONDITION = -2;
//与共享锁的唤醒有关
static final int PROPAGATE = -3;

通过以上代码,我们就可以大概了解queue的结构了
在这里插入图片描述

加锁过程

acquire()

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

arg代表着state要被赋予的新值,如果是独占锁的话,就是arg=1。
通过代码我们看出首先会调用tryAcquire(arg)

tryAcquire()

 protected boolean tryAcquire(int arg) {
      throw new UnsupportedOperationException();
  }

你一定会很奇怪,为什么会直接抛出一个异常?
AQS并不能直接提供锁工作,而是作为一个基础类来供调用来自定义锁工具。自定义的锁工具要继承AQS来自定义首先tryAcquire()方法,如果没有自定义tryAcquire()的话,就会抛出异常。

本文的话就按照ReentrantLock的自定义对象来分析一下

protected final boolean tryAcquire(int acquires) {
	//会调用nonfairTryAcquire()
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
	//获取到当前请求锁的线程对象
    final Thread current = Thread.currentThread();
    //获取当前的state
    int c = getState();
    //c==0说明当前锁对象可用
    if (c == 0) {
        //因为有可能多个线程同时请求锁,所以利用CAS来原子更新
        if (compareAndSetState(0, acquires)) {
            //更新成功的话,就说明当前对象获得了锁对象,设置exclusiveOwnerThread为当前线程对象
            setExclusiveOwnerThread(current);
            //返回true 代表请求锁成功
            return true;
        }
    }
    //如果当前线程已经持有当前锁,也就是现在线程重入了
    else if (current == getExclusiveOwnerThread()) {
        //将state++;
        int nextc = c + acquires;
        //超过了int的上线,也就是重入的次数太多了,抛异常。。。
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        //设置state,这个时候没有利用CAS,因为每次只有一个线程才能持有锁,肯定不会冲突
        setState(nextc);
        return true;
    }
   //既不是当前持有锁的线程,也没有抢锁成功,返回false
    return false;
}

addWaiter()

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

如果tryAcquire()返回true就说明加锁成功了。

如果返回false,代表加锁失败,将当前对象加入到等待queue中。

首先执行addWaiter(Node.EXCLUSIVE)

private Node addWaiter(Node mode) {
	//利用当前线程和mode(独占EXCLUSIVE)创建node
    Node node = new Node(Thread.currentThread(), mode);
    //获得尾节点
    Node pred = tail;
    //队列不为空,只尝试着入队一次,如果入队不成功,就执行enq()
    if (pred != null) {
    	//将node的prev设置为tail
        node.prev = pred;
        //交换node和tail,如果CAS成功,node就变成了新的tail
        if (compareAndSetTail(pred, node)) {
            //将之前tail的next变为node,
            pred.next = node;
            return node;
        }
    }
    //之前的一次入队没有成功,执行enq()
    enq(node);
    return node;
}

首先利用线程对象Thread.currentThread()和mode(Node.EXCLUSIVE)创建了一个Node,要注意新入队的node的waitStatus是默认值0

然后将tail赋值给变量pred,分为两种情况:
1、pred!=null时,说明当前queue不为空,则进行CAS操作,将node和tail交换,如果成功的话,就直接返回node,node现在已经变为了tail了,因为CAS是比较和交换的,所以不用在重新将tail赋值了。

2、pred == null,说明当前queue为空,则调用enq(node)

private Node enq(final Node node) {
	//死循环
    for (;;) {
        Node t = tail;
        //如果队列为空
        if (t == null) { 
            //创建一个哨兵空节点(并非null)并CAS设置为head
            //队列的初始化是延迟加载的 lazy initialize
            if (compareAndSetHead(new Node()))
                //将tail指向head,接着循环,将node加入到队尾。
                tail = head;
        } else {
            node.prev = t;
            //CAS将node设置为tail
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

从上面代码我们可以看出,将node加入到队尾的时候,如果queue队列还没初始化,会先将head和tail初始化一个哨兵空节点,然后再循环CAS来将node加入到队尾。

所以说,queue的对应的头一直是空节点(不是null),头节点是一个哨兵节点,queue的准确结构如下:
在这里插入图片描述

从上述代码我们可以看出,入队有三个步骤
1、将node.prev设置为当前的tail
2、CAS的将tail和node交换位置
3、将node.next设置之前的tail
在这里插入图片描述

这三个操作并不是原子的。当出现多个线程同时入队的时候,每个线程都能完成步骤1,也就是成功设置完prev,但是CAS并不一定会成功,出现一种尾分叉的现象。

尾分叉

在这里插入图片描述

acquireQueued()

当完成addWaiter()后,就会执行acquireQueued()方法。

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

接下来我们看acquireQueued方法的代码

final boolean acquireQueued(final Node node, int arg) {
    //这时node会尝试再次抢夺锁,failed代表是否抢锁失败。
    boolean failed = true;
    try {
        //线程是否被其他线程调用了中断
        boolean interrupted = false;
        for (;;) {
            //node的prev节点
            final Node p = node.predecessor();
            //如果p是头节点,也就是node是第二个节点,会尝试抢锁
            if (p == head && tryAcquire(arg)) {
                //如果抢到锁,将node设为head
                setHead(node);
                p.next = null; 
                failed = false;
                
                //抢到锁后返回
                return interrupted;
            }
            
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

如上述代码所示,如果node处于当前第二个节点的话,就试图去抢锁,如果抢到直接返回,抢不到的话,进去shouldParkAfterFailedAcquire(),看方法名字应该是在抢锁失败后,将线程挂起。

shouldParkAfterFailedAcquire()

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获得node的前继节点pred的waitStatus
    int ws = pred.waitStatus;
     //如果前继节点pred的状态是SIGNAL,就返回true
     if (ws == Node.SIGNAL)
         return true;
     //ws大于0,代表pred节点对应的waitStatus为cancel,也就是线程放弃锁了,
     if (ws > 0) {
         //这段代码是将waitStatus为cancel的节点给踢出
         do {
             node.prev = pred = pred.prev;
         } while (pred.waitStatus > 0);
         pred.next = node;
     } else {//waitStatus等于0,说明pred是新插入的节点或者是head,因为只有新插入节点和head的waitStatus为0
         //将pred的status更新为SIGNAL,并返回false,最终拼搏一次抢锁
         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     }
     return false;
 }

从上述代码可以看出,shouldParkFailedAcquire方法的作用是
1、如果pred的waitStatus是SIGNAL,就返回true
2、如果pred的waitStatus是大于0,就将放弃锁的节点全部剔出,并将node的prev节点设置为waitStatus小于等于0的节点,返回false
3、如果pred的waitStatus小于0,就将pred的waitStatus设置为SIGNAL,返回false。

我们回到方法调用处

final boolean acquireQueued(final Node node, int arg) {
    //这时node会尝试再次抢夺锁,failed代表是否抢锁失败。
    boolean failed = true;
    try {
        //线程是否被其他线程调用了中断
        boolean interrupted = false;
        for (;;) {
            //node的prev节点
            final Node p = node.predecessor();
            //如果p是头节点,也就是node是第二个节点,会尝试抢锁
            if (p == head && tryAcquire(arg)) {
                //如果抢到锁,将node设为head
                setHead(node);
                p.next = null; 
                failed = false;
                
                //抢到锁后返回
                return interrupted;
            }
            //这里这里这里
            //这里这里这里
            //这里这里这里
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

从上述代码可以看出,除了pred的waitStatus为true的时候,会返回true,这个时候会进去parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
    //线程被挂起
    LockSupport.park(this);
    return Thread.interrupted();
}

从上面的parkAndCheckInterrupt可以看出当返回true的时候,也就是node的前继节点pred的waitStatus是SIGNAL时,会将node对应的线程挂起。
SIGNAL是什么意思呢?唤醒。这也就是说明,当一个节点的waitStatus为SIGNAL时,它在放弃锁或者释放锁的时候需要将后面的节点从挂起状态唤醒。

如果shouldParkAfterFailedAcquire()返回false的话,说明node对应的前继节点pred放弃锁了,将pred剔除后,node很有可能就来到了queue的第二个节点(head是哨兵空节点),获得锁的概率很大,会尝试着抢夺一下锁,当抢夺不到锁的时候,才会再次尝试执行shouldParkAfterFailedAcquire(),也就是将线程挂起。

说到这里,我们就将抢夺锁的流程大致说完了,我们来总结一下:

抢锁流程概述:

1、执行tryAcquire()方法抢锁,tryAcquire()方法是锁工具自定义实现的。抢锁成功后就直接执行同步区代码
2、如果抢锁不成功,执行addWaiter()方法:将线程包装为Node,利用自旋的CAS将Node加入到queue中。
3、加入Queue后,会尝试将线程挂起。
3.1、如果线程对应的node处于queue的第二个节点,因为head是哨兵空节点,这个时候抢夺锁成功的概率会很大,会尝试再次抢夺锁
3.2、如果线程node不满足抢夺锁的条件,会尝试挂起,
3.2.1、如果node的前继节点prev的waitStatus是Signal则直接会 被挂起
3.2.2、如果node的前继节点prev的waitStatus是Cancel,则会循环将waitStatus为cancel的节点都剔除出去,这个时候,node很有可能就会满足抢夺锁的条件,会尝试抢夺锁,如果抢夺失败就将prev的waitStatus改为Signal,然后将线程挂起。
3.2.3、如果node的前继节点prev的waitStatus < 0,说明prev为head或者是新插入的节点,会将其prev的waitStatus设置为Signal,然后返回false,最后拼搏抢夺一次锁。

解锁

接下来我们看下解锁流程

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

如上述代码所示,首先会调用tryRelease()

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

从上面我们可以看出,tryRelease和tryAcquire一样,都是需要锁工具自定义的,下面我们就看下ReentrantLock的tryRelease方法

protected final boolean tryRelease(int releases) {
   //将state减去1
   int c = getState() - releases;
   //如果释放锁定线程和拥有锁的线程不一致,说明程序出错了,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //如果c == 0,说明释放锁了,如果c>0不释放锁,因为ReenTrantLock是可重入的
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    //如果要释放锁,就返回true
    return free;
}

我们在回到方法调用处

public final boolean release(int arg) {
    if (tryRelease(arg)) {
       //这里这里这里
       //这里这里这里
       //这里这里这里
        Node h = head;
        //如果h == null或者h的waitStatus等于0说明当前的queue中没有线程等待锁啊。
        //如果有线程等待锁的话,head的waitStatus应该是Signal的,是不等于0的。
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

接下来我们说下unparkSuccessor()

    private void unparkSuccessor(Node node) {
    
    int ws = node.waitStatus;
    //如果waitStatus<0,说明head的waitStatus为Signal,需要将后继节点唤醒
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    
    if (s == null || s.waitStatus > 0) {
        //你一定很奇怪,为什么s都为null了,为什么还会继续遍历,因为我们当初尾分叉的时候,说过,入队的时候,是先将node.prev = tail,所以虽然s==null,可能node的后继是有节点的,只是还未被链接上
        // s.waitStatus > 0 说明s取消锁了
        s = null;
        //采用的做法是,从后往前遍历,找到最后一个不被cancel的节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //将其唤醒
        LockSupport.unpark(s.thread);
}

唤醒了之后呢?对应的线程会从挂起时的代码开始执行

private final boolean parkAndCheckInterrupt() {
    //线程被挂起
    LockSupport.park(this);
    //这里这里这里
    return Thread.interrupted();
}

会返回线程是否被中断的boolean变量,我们继续往上看

final boolean acquireQueued(final Node node, int arg) {
    //这时node会尝试再次抢夺锁,failed代表是否抢锁失败。
    boolean failed = true;
    try {
        //线程是否被其他线程调用了中断
        boolean interrupted = false;
        for (;;) {
            //node的prev节点
            final Node p = node.predecessor();
            //如果p是头节点,也就是node是第二个节点,会尝试抢锁
            if (p == head && tryAcquire(arg)) {
                //如果抢到锁,将node设为head
                setHead(node);
                p.next = null; 
                failed = false;
                
                //抢到锁后返回
                return interrupted;
            }
            //这里这里这里
            //这里这里这里
            //这里这里这里
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果出现异常情况,没有获得到锁,就将node给取消,从队列中移除
        if (failed)
            cancelAcquire(node);
    }
}

如果返回true的话,就是将interrupted赋值为true,表示线程被中断过,
然后会继续抢夺锁,如果没抢到还是会被挂起。
如果抢到的话,会回到调用方法的地方,并返回interrupted的值

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

从上述代码看,
1、如果interrupted为false,代表着线程抢锁成功,可以执行同步区代码了。

如果interrupted为true,则会执行selfInterrupt()方法

static void selfInterrupt() {
     //中断线程,具体的中断处理,还是线程自己安排是否要放弃锁之类的。
     //中断并不会直接将线程停止运行,这个需要线程自己把握
     Thread.currentThread().interrupt();
 }

独占锁总结

1、首先执行tryAcquire抢锁,tryAcquire的逻辑代码由锁工具自己实现,AQS对应的方法是抛出异常,如果不实现执行AQS的方法抛异常,
如果抢锁成功,就直接执行同步区代码,抢锁不成功,转到2。
2、执行addWaiter(),会将thread对象包装问一个Node,waitStatus=0,Mode = EXCLUSIVE,然后通过CAS将其插入到锁queue中,如果锁queue为空,会先将queue插入一个空Node,然后在执行插入Node。
3、执行acquireQueued(node),如果node满足是queue的第二个节点,会尝试抢锁,如果抢锁成功,会返回其是否被Interrupt,如果被中断了,会执行自我中断,如果没有中断,抢锁流程结束。
4、如果node不满足抢锁条件或者没有抢到锁,就开始着手挂起了,来节省cpu资源。
如果node的前继节点的waitStatus=SIGNAL,就直接挂起。
如果node的前继节点的waitStatus=CANCEL,就会依次遍历将前继节点的waitStatus=CANCEL的节点都出队,然后再次抢锁,这时很有可能就满足抢锁条件了,也就是转到3.
如果node的前继节点的waitStatus=0,就会将CAS操作,将前继节点的waitStatus改为SIGNAL,然后也继续抢锁,也就是转到3。
5、如果线程要释放锁了,如果head不为null,并且waitStatus等于Signal,这时就说明后面有需要被唤醒的线程。这时候会从后往前来依次遍历线程,将其唤醒,为什么会从后往前呢,因为在addWaiter()的时候,需要三步、node.prev = tail, CAS(tail,node),tail.prev = node。这三步不是原子的,很有可能只执行到前两不,这时node已经在queue上了,但是前继节点还未与node连接,只能从后边遍历。
6、找到最前面的node,将其唤醒。这时又回到了步骤3,线程开始抢锁,只不过在在抢锁之前会判断是否在挂起期间被中断了,如果被中断了,在抢锁成功之后,会执行自我中断。

tip

抢锁成功分为两种
1、直接抢锁成功,没有进入过锁queue
2、进入过锁queue,被唤醒后才抢锁成功

这两种有一个区别,第二种抢锁成功后,会将node从queue中移除,利用的就是将头节点往后移动,并将node的thread清除。因为node只有在是锁queue中的第二个节点才有资格抢锁。

共享锁加锁

tryAcquireShared()

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

tryAcquireShared()也是锁工具了自己要实现的方法,
1、如果返回大于0,当前线程加锁成功
2、如果返回等于0.当前线程加锁成功,但是下一个线程就不能加锁了
3、如果返回小于0,说明线程加锁失败

doAcquireShared()

private void doAcquireShared(int arg) {
   //和之前独占锁一样,将node加入到queue中,只不过mode是Node.SHARE
   final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                
                if (r >= 0) {//抢锁成功
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            //尝试将线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

首先会进行addWaiter(),只不过传入的是Node.SHARED,然后

//addWaiter()
Node node = new Node(Thread.currentThread(), mode);
Node(Thread thread, Node mode) {     // Used by addWaiter
    this.nextWaiter = mode;
    this.thread = thread;
}

和独占锁不一样的是,独占锁的nextWaiter赋值为Node.EXCLUSIVE

addWaiter后的流程和独占锁类似,都是满足是queue的第二个节点的时候,调用tryAcquireShared()请求锁,请求不到的话,就尝试挂起,挂起的判断逻辑都和独占锁一样。

不同的就是当抢锁成功后的setHeadAndPropagate,而独占锁的是setHead,接下来我们对比一下

//独占锁的就是将node设置为head,很简单
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    //将node设置为head
    setHead(node);

	//如果propagate > 0说明共享锁还可以容纳其他线程来加锁
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        //如果s为争抢共享锁的线程对象,为null也区唤醒,因为是存在尾分叉的
        if (s == null || s.isShared())
            //唤醒后面的线程来争夺共享锁
            doReleaseShared();
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        //如果queue不为空
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            //head节点后面有需要唤醒的节点
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;   
                //唤醒后面的线程来加锁         
                unparkSuccessor(h);
            }//ws == 0 说明此时队列尾节点刚加入,还未将prev也就是head的waitStatus设置为Signal, 如果下面的CAS失败,说明上面的head.waitStatus = signal成功了,还需要继续执行循环,需要跳过 h == head
              //如果head.waitStatus = signal失败了也不要紧,这时head的propagate < 0 在运行shouldParkAfterFailedAcquire(p, node)后会返回false,还会再一次进行争夺锁。
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                
        }
        
		//1、当有其他线程被唤醒并成功获取锁的时候,head就会发生改变,这时h != head ,如果线程被唤醒后没有成功获取到锁,这时h == head,会推出死循环
		
		//2、如果没有其他线程被唤醒成功获取锁,h == head,但是此时有新节点加入并未将h的waitStatus改为Signal,并且CAS成功,那么compareAndSetWaitStatus(h, 0, Node.PROPAGATE)会失败,这时ws变成了Signal,还需要执行循环,所以会continue会跳过下面的代码,继续循环。
        if (h == head)                   
            break;
    }
}

共享锁解锁

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

如上述代码,当释放共享锁成功后,执行doReleaseShared();

上面我们都分析过了,会唤醒线程来争夺共享锁。

从上面的doReleaseShared()方法可以看出,AQS设计的太巧妙了。

共享锁总结

共享锁的流程和独占锁类似,有以下区别
1、锁工具对应的tryAcquire()实现方法不一样,tryAcquire()的返回值如果大于等于0代表抢锁成功,大于0代表还有剩余资源供其他线程抢夺。
2、共享锁除了在释放锁的时候唤醒线程,在抢占成功后,如果返回值大于0,也会从尾到头唤醒线程来抢夺锁,加快了唤醒线程的速度。

Condition

我们前面说过AQS和Synchronized的一个区别就是AQS提供了Condition队列来精确的唤醒对应的等待线程。

Condition实例

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    // 生产者方法,往数组里面写数据
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await(); //数组已满,没有空间时,挂起等待,直到数组“非满”(notFull)
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            // 因为放入了一个数据,数组肯定不是空的了
            // 此时唤醒等待这notEmpty条件上的线程
            notEmpty.signal(); 
        } finally {
            lock.unlock();
        }
    }

    // 消费者方法,从数组里面拿数据
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await(); // 数组是空的,没有数据可拿时,挂起等待,直到数组非空(notEmpty)
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            // 因为拿出了一个数据,数组肯定不是满的了
            // 此时唤醒等待这notFull条件上的线程
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

Condition的主要方法:
Condition.await():等待条件,将线程加入到Condition队列中
Condition.asignal():唤醒对应的Condtion队列中的线程。

Condition的组成结构
Condition的原理,其实也是一个Condition对应着一个Queue,其节点也是Node,只不过只用到了三个属性

thread:对应的线程
waitStatus:等待状态 ,只有两种Node.condition和其他
nextWaiter:队列中的下一个节点

Condition Queue 和之前的加锁的queue的联系
在这里插入图片描述
工作流程:
1、如果调用了await(),condition的相关方法只能在获取到锁之后才能调用,所以这时线程已经获取到了锁,这时线程需要放弃锁,然后将线程包装成Node加入到对应的condition队列中
2、如果其他线程调用了asignal(),会唤醒对应的condition的等待队列中的一个线程,线程还需要进一步获取锁,之前的锁在await()的时候已经释放了。

1、通过lock.newCondtion()来创建Condition

Condition c = lock.newCondition();

我们来一步步看下newConditon()的源码

final ConditionObject newCondition() {
  return new ConditionObject();
}

从上述代码看,就是新建了一个ConditionObject对象。

//空构造方法,什么都没干
public ConditionObject() { }

Condition是一个接口,ConditionObject是Condition的一个子类。

ConditionObject有以下两个重要的属性

//condition对应的队列的头节点
private transient Node firstWaiter;
//condition对应的队列的尾节点
private transient Node lastWaiter;

从上面我们可以看出,Condition就是一个对应的队列

await()

public final void await() throws InterruptedException {
    //如果线程被中断了,就抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //向队列中加入Node
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

addConditionWaiter()
从上面代码看首先会执行addConditionWaiter()

private Node addConditionWaiter() {
    Node t = lastWaiter;
    //如果队列不为空,并且tail已经出队了
    //当t的waitStatus不为Node.CONDITION的时候,就说明已经被唤醒过了
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
	//同时只有一个线程执行await,不会出现并发问题
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        //Node之间关系是通过nextWaiter来联系的,Conditon队列是个单向链表
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

如上述代码所示,就是普通的入队操作,只不过会牵扯到已经被signal唤醒的线程会被踢出queue,代码如下

private void unlinkCancelledWaiters() {
	//就是踢出所有waitStatus不为Node.CONDITION的node
   Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            //帮助垃圾回收,断除与其他node的联系
            t.nextWaiter = null;
            //如果trail == null 说明 firstWater也不是Node.CONDITION,需要被踢出,将其赋值为next
            if (trail == null)
                firstWaiter = next;
            else//将t踢出
                trail.nextWaiter = next;
             //循环结束,指定lastWaiter为trail
            if (next == null)
                lastWaiter = trail;
        }
        else
        //trail赋值为最新的waitStatus为Node.CONDITION的node,
            trail = t;
        t = next;
    }
}

然后我们再次回到方法的调用处

public final void await() throws InterruptedException {
    //如果线程被中断了,就抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //向队列中加入Node
    Node node = addConditionWaiter();
    //这里这里这里
    //这里这里这里
    //这里这里这里
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

从上述代码可以看出,接着会调用fullyReleas(node),也就是将锁释放

final int fullyRelease(Node node) {
    //是否释放失败?
    boolean failed = true;
    try {
        //获得当前state
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        //如果释放失败,将node的waitStatus = cancel
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

主要的释放代码在release(saveState)中,代码如下:

public final boolean release(int arg) {
    //尝试着释放锁
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

tryRelease(arg)是释放锁,

// arg == state
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

AQS中对应的直接抛出异常,这个方法需要锁工具类自己实现释放的逻辑,我们来看下ReentrantLock的实现,代码如下:

protected final boolean tryRelease(int releases) {
   int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

从上述代码可以看出,由于传入的releases是state,所以c = 0,会将线程对应的锁释放。

让我们回到tryRelease方法的调用处

```java
public final boolean release(int arg) {
    //尝试着释放锁
    if (tryRelease(arg)) {
       //这里这里这里
       //这里这里这里
        Node h = head;
        //如果queue不为空
        if (h != null && h.waitStatus != 0)
             //唤醒queue中的线程来争夺锁
            unparkSuccessor(h);
        return true;
    }
    //释放失败
    return false;
}

让我们再次回到release()方法的调用处

final int fullyRelease(Node node) {
    //是否释放失败?
    boolean failed = true;
    try {
        //获得当前state
        int savedState = getState();
        //这里这里这里
        //这里这里这里
        if (release(savedState)) {
            failed = false;
            //返回savedState
            return savedState;
        } else {
            //如果释放失败,就报异常
            throw new IllegalMonitorStateException();
        }
    } finally {
        //如果释放失败,将node的waitStatus = cancel
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

让我们回到fullyRelease的调用处

public final void await() throws InterruptedException {
    //如果线程被中断了,就抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //向队列中加入Node
    Node node = addConditionWaiter();
    //这里这里这里
    //这里这里这里
    //这里这里这里
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

接着会调用isOnSyncQueue()

final boolean isOnSyncQueue(Node node) {
    //如果waitStatus为CONDITION 或者 prev == null,肯定是在condition queue中
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    //如果next!=null,这时肯定在锁queue中了
    if (node.next != null) 
        return true;
   
    //从锁queue的tail到head来遍历检查node是否在锁queue上
    return findNodeFromTail(node);
}

从上述代码可以看出,会检测node是否在锁queue上

不对劲啊,我们不是刚将node加入到condition queue上吗?怎么会跑到锁queue上呢?我们这里先不细纠结原因,等待signal的时候,我们就会明白了。

isOnSyncQueue()的作用是如果node如果在锁queue上,就直接跳过线程挂起的操作。

LockSupport.park(this);

如果不在锁queue上,就挂起。

好了 await我们先说到这,接着我们说signal。因为只有signal后,线程才会被唤醒。

Signal

public final void signal() {
    //判断当前线程是否拥有锁,这个方法是由所工具类自定义实现的。共享锁和独占锁的逻辑不一样
    if (!isHeldExclusively())
        //如果不拥有锁,说明出错了
        throw new IllegalMonitorStateException();
    //condition queue中的第一个节点    
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

如果condition不为空,会调用doSignal()

private void doSignal(Node first) {
   do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

我们看到,首先会调用 transferForSignal()

final boolean transferForSignal(Node node) {
     //node就是firstWaiter,会首先将node的waitStatus从CONDITION改为0,如果失败会返回false,结合到方法调用处的while循环,如果firstWaiter不为null,就会一直调用
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    //将firstWaiter入队,返回的节点是firstWaiter的prev前继节点
    Node p = enq(node);
    int ws = p.waitStatus;
    //1、如果前继节点ws>0也就是放弃锁了,就会唤醒线程
    //2、如果前继节点ws为0,但是将其ws改为Singal失败的话,也会将线程唤醒
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

signalAll

singalAll就是唤醒condition中的全部节点

public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}

和signal方法大致一样,只不过signal调用的是doSignal,而signalAll调用的是doSignalAll。

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

从代码就可以看出,和doSignal的大致原理一样,都是调用transferForSignal,但是会将所有节点都出队然后调用transferForSignal。

await唤醒之后

线程被signal后,我们发现node被插入了锁queue,然后我们接着看await

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //这里这里
        //这里这里
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

首先我们说下为什么会判断isOnSyncQueue,因为存在这种可能,就是某个线程在刚被addConditionWaiter之后,对应的condition就signal了,将其放入了锁queue,这时该线程就没有必要在挂起了

我们先说下被挂起,然后被唤醒的流程,最后说不被挂起的流程
挂起被唤醒之后,会调用

interruptMode = checkInterruptWhileWaiting(node)
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}
final boolean transferAfterCancelledWait(Node node) {
    //将node的waitStatus从Conditon改为0,
    //如果signal成功的话,waitStatus肯定是0,CAS会失败,会返回false
    //如果signal异常的话,waitStatus为CONDITION,cas如果成功,会让其继续入队,返回true。如果cas失败的话,会检查node是否在锁queue中,如果在就返回false,如果不在,就让线程终止。
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

从上述代码可以看出,分为三种情况
1、如果线程被中断,会调用transferAfterCancelledWait,如果在signal的过程中,发生异常的话,让interruptMode = THROW_IE,说明这时出错了,抛出异常。
2、如果线程被中断,signal没发生异常,就说明仅仅被调用了interrupt方法,interruptMode = REINTERRUPT,说明需要处理中断
3、如果没被中断的话,就返回0,代表没有中断

我们接着回到方法调用处

```java
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        //这里这里
        //这里这里
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //这里这里
    //这里这里
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

接着会调用acquireQueued,acquireQueued我们之前说过,如果node处于锁queue的第二节点,就回去抢锁,抢锁成功就返回是否被中断,如果不成功,就将前继节点的waitStatus设为signal,然后挂起。

如果返回为true的话,说明线程在抢锁期间被中断了,
如果之前signal期间没有异常的话,就设置

interruptMode = REINTERRUPT;

也就是线程需要中断,具体的中断处理交给线程自己决定

接下来,如果node.nextWaiter != null,说明线程没有经过挂起,也就是没有经过signal,需要将nextWaiter设置为null

最后会调用

reportInterruptAfterWait(interruptMode);
private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
     //如果signal异常,就抛异常
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
        //没有异常就只有线程被中断,就调用中断
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

没有被挂起的线程的情况和挂起的线程的情况类似,就少了一个THROW_IE,其他完全一致!

Condition总结

1、首先会生成新的Node ,node.waitStatus = CONDITION ,会利用nextWaiter属性来Node组成一个condition queue。
2、接着会释放锁,唤醒锁queue上的节点来争夺锁
3、这时会判断如果node在锁queue中,说明此时其他线程signal的速度很快,condition条件已经满足,转向5
4、如果node不在锁queue中,将线程挂起,等待signal
5、当其他线程signal时,会将condition queue中的头节点firstWaiter从condition queue中踢出,然后将其waitStatus改为0,并加入到锁queue中,如果firstWaiter的前继节点prev放弃锁了,或者将prev的waitStatus改为signal失败后,会唤醒节点。

signalAll是将所有节点都调用signal(),具体调用哪个根据实际需要使用

6、当线程被唤醒后,如果是之前被挂起的,会先判断其waitStatus是否是0,如果不是0,说明在之前signal的时候出现异常了,将其interruptMode设置为THROW_IE,如果被中断的话,将其interruptMode设置为REINTERRUPT,如果没有被中断的话,就设置为0
如果是没有被挂起的直接转到7,interruptMode=0,

7、接着执行acquireQueued,这个方法就是抢夺锁,如果抢到就返回是否被中断的标记,如果抢不到就挂起。
如果抢锁成功后,会判断interruptMode是否等于THROW_IE,如果不等于,interruptMode = acquireQueued()

8、如果node.nextWaiter!=null,将其设置为null,这种情况出现在线程未挂起的时候

9、通过interruptMode!=0,就直接运行同步区代码
如果interruptMode == REINTERRUPT,就自我中断,具体的逻辑线程自己处理
如果interruptMode == THROW_IE,就抛出异常,具体的逻辑也是线程自己在catch 代码块中处理。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值