AQS源码详细解读

1.什么是AQS

AQS全名:AbstractQueuedSynchronizer,它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。

2.AQS的核心思想

AQS核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

3.ReentrantLock

AQS源码详细解读_第1张图片

4.ReentrantLock构造器

无参构造器:默认非公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

有参构造器:fair参数来判断是否公平锁

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

5.ReentrantLock数据结构

AbstractQueuedSynchronizer属性

private transient volatile Node head;
private transient volatile Node tail;
//state>0表示被线程持有 等于0表示没有被任何线程持有
private volatile int state;

AbstractQueuedSynchronizer中Node节点属性:

static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
//由于超时或中断,此节点被取消
static final int CANCELLED =  1;
//此节点后面的节点被阻塞(park),避免竞争,资源浪费,此节点释放后通知后面的节点
static final int SIGNAL    = -1;
//表示这个Node在条件队列中,因为等待某个条件而被阻塞
static final int CONDITION = -2;
//使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
//线程对象
volatile Thread thread;
Node nextWaiter;

6.lock方法源码分析

(1)公平锁
FairSync.lock
final void lock() {
    acquire(1);
}
AQS.acquire

1表示可重入锁重入次数

public final void acquire(int arg) {
  //tryAcquire尝试获取锁,如果获取不到则添加到AQS的队列中
    if (!tryAcquire(arg) && 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
FairSync.tryAcquire
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
  //获取锁的状态
    int c = getState();
  //state==0未被任何线程持有
    if (c == 0) {
      //hasQueuedPredecessors判断是否可以尝试获取锁,compareAndSetState使用CAS尝试修改state获取锁
        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;
}
AQS.hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
  //用h!=t表明队列里有数据
  //head.next== null表明还处于半初始化状态,肯定别的线程在操作,已经领先当前线程了
  //s.thread != Thread.currentThread()表明当前线程并不是即将要获取锁的线程
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
AQS.addWaiter
private Node addWaiter(Node mode) {
  //创建新的节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
  //当tail节点不为空的时,仅仅是尝试一次快速添加节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
  //上一步失败则通过enq入队
    enq(node);
    return node;
}
AQS.enq
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
      //如果tail为空(队列为空)
        if (t == null) { // Must initialize
          //创建一个空的标志节点作为head节点,并将tail也指向它
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
          //正常流程,放入队尾,CAS将当前节点node设置为tail
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
AQS.acquireQueued
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {//死循环
          //获取该节点的前置节点
            final Node p = node.predecessor();
          //如果前置节点是head,表明之前的节点是正在运行的线程,表示是第一个排队的
            if (p == head && tryAcquire(arg)) {
              //占有锁成功,将当前节点设置为head
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
          //
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
AQS.cancelAcquire
private void cancelAcquire(Node node) {
    // 如何节点不存在则忽略
    if (node == null)
        return;

    node.thread = null;

    // 遍历更新前驱节点,把node的pre指向最近的一个非取消节点 pred.waitStatus > 0 表示取消状态(-1)
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    //记录pred节点的后继,后续CAS会用到
    Node predNext = pred.next;

    //将当前节点的状态设置为取消
    node.waitStatus = Node.CANCELLED;

		//如果当前节点是tail节点,将当前节点的前驱设置为tail
    if (node == tail && compareAndSetTail(node, pred)) {
      	//如果上一步设置成功,将pred的后继结点设置为null,切断pred和node的联系
        compareAndSetNext(pred, predNext, null);
    } else {       
      // 如果node还有后继节点,这种情况要做的事情是把pred和后继非取消节点拼起来。
        int ws;
      //三个条件:
      //1.pred节点不是head 
      //2.pred节点的状态是SIGNAL 或者 ws小于0的情况下CAS设置ws为SIGNAL
      //3.pred.thread != null
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
          //如果node的next节点为非取消状态,则用CAS尝试把pred的后继置为node的后继节点
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
          //三种情况:1.pred == head 2.pred状态取消 3.pred.thread == null
          //在这些情况下为了保证队列的活跃性,需要去唤醒一次后继线程。
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
AQS.unparkSSuccessor
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
  //如果node的等待状态为非取消状态(为SIGNAL -1 或 CONDITION -2 或 PROPAGATE -3),将等待状态设置为初始值0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
  //如果node.next存在并且状态为取消状态
    if (s == null || s.waitStatus > 0) {
        s = null;
      //从tail节点开始,循环向前找到下一个最近的非取消的节点赋值:s = t
      //为什么是从tail开始?
      //其最根本的原因在于:在enq方法中node.prev = t;先于CAS执行,也就是说,你在将当前节点置为尾部之前就已经把前驱节点赋值了,自然不会出现prev=null的情况。如果是从head开始,高并发情况下会出现后续节点被漏的情况
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
  //如果node.next存在并且状态不为取消,则直接唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}
AQS.setHead
private void setHead(Node node) {
    head = node;//设置当前节点为head
    node.thread = null;
    node.prev = null;//当前节点已经是head节点,前驱节点当然是设置null
}
AQS.shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
  //前驱节点设置为SIGNAL(通知)状态,它运行完了会通知一下,可以挂起了
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
      //前驱节点cancle了,一直往前找,找到最近的一个正常状态的节点,并且排在它后面,被放弃的节点GC回收
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
      //如果是正常状态的前驱节点,CAS尝试把它设置为signal状态,有可能会失败,手不定它刚释放锁
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
AQS.parkAndCheckInterrupt
//前驱节点已经设置为SIGNAL状态了,调用park方法进去waiting状态,休息等待
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的
}
(2)非公平锁

NonFairSync.lock

final void lock() {
  //首先尝试获取锁,compareAndSetState使用CAS尝试修改state获取锁
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
AQS.acqurie
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
NonFairSync.tryAcquire
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
Sync.nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
      //此处公平锁不同,无需使用hasQueuedPredecessors判断是否可以尝试获取锁
        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;
}

7.unlock方法源码分析

AQS.release
public final boolean release(int arg) {
  //尝试解锁
    if (tryRelease(arg)) {
        Node h = head;
      //如果后继还有线程排队
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
AQS.tryRelease
protected final boolean tryRelease(int releases) {
  //首先state-1
    int c = getState() - releases;
  //如果当前线程不是持有锁的线程,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
  //如果state==0则表示锁已经释放掉了,将持有线程的exclusiveOwnerThread设置为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
  //更改state的值
    setState(c);
    return free;
}

8.Condition详解

(1)conditon介绍

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。
Condition是个接口,基本的方法就是await()和signal()方法;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition();调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

(2)Condition与Object对比
  • Conditon中的await()对应Object的wait();
  • Condition中的signal()对应Object的notify();
  • Condition中的signalAll()对应Object的notifyAll()。

AQS源码详细解读_第2张图片

(3)ConditionObject.await源码
ConditionObject.await
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
  //创建一个新的节点,追加到condition队列中的最后一个节点
    Node node = addConditionWaiter();
  //释放这个锁,并唤醒AQS中的一个线程
    int savedState = fullyRelease(node);
    int interruptMode = 0;
  //判断这个节点是否在AQS队列上,第一次判断总是返回fase
    while (!isOnSyncQueue(node)) {
      //第一次总是park自己,开始阻塞等待
        LockSupport.park(this);
      // 线程判断自己在等待过程中是否被中断了,如果没有中断,则再次循环,会在 isOnSyncQueue 中判断自己是否在队列上.
      //  isOnSyncQueue 判断当前 node 状态,如果是 CONDITION 状态,或者不在队列上了(JDK 注释说,由于 CAS 操作队列上的节点可能会失败),就继续阻塞.
      //  isOnSyncQueue 判断当前 node 还在队列上且不是 CONDITION 状态了,就结束循环和阻塞.
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
          //如果被中断了就跳出循环
            break;
    }
  	// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
    // interruptMode != THROW_IE >>> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让其入队了.
    // 将这个变量设置成 REINTERRUPT.
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
  	// 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点. 
    // 如果是 null ,就没有什么好清理的了.
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
  // 如果线程被中断了,需要抛出异常.或者什么都不做
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

方法逻辑总结:

  • 在 Condition 中, 维护着一个队列,每当执行 await 方法,都会根据当前线程创建一个节点,并添加到尾部.
  • 然后释放锁,并唤醒阻塞在锁的 AQS 队列中的一个线程.
  • 然后,将自己阻塞.
  • 在被别的线程唤醒后, 将刚刚这个节点放到 AQS 队列中.接下来就是那个节点的事情了,比如抢锁.
  • 紧接着就会尝试抢锁.接下来的逻辑就和普通的锁一样了,抢不到就阻塞,抢到了就继续执行.
ConditionObject.addConditionWaiter
// 该方法就是创建一个当前线程的节点,追加到最后一个节点中.
private Node addConditionWaiter() {
  // 找到最后一个节点,放在局部变量中,速度更快
    Node t = lastWaiter;
// 如果最后一个节点失效了,就清除链表中所有失效节点,并重新赋值 t
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
  // 创建一个当前线程的 node 节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
  // 如果最后一个节点是 null
    if (t == null)
      // 将当前节点设置成第一个节点
        firstWaiter = node;
    else
      // 如果不是 null, 将当前节点追加到最后一个节点
        t.nextWaiter = node;
  // 将当前节点设置成最后一个节点
    lastWaiter = node;
    return node;
}
ConditionObject.unlinkCancelledWaiters

清除链表中所有失效的节点

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
  // 当 next 正常的时候,需要保存这个 next, 方便下次循环是链接到下一个节点上.
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
      // 如果这个节点被取消了
        if (t.waitStatus != Node.CONDITION) {
          // 先将他的 next 节点设置为 null
            t.nextWaiter = null;
          // 如果这是第一次判断 trail 变量
            if (trail == null)
              // 将 next 变量设置为 first, 也就是去除之前的 first(由于是第一次,肯定去除的是 first)
                firstWaiter = next;
            else
              // 如果不是 null,说明上个节点正常,将上个节点的 next 设置为无效节点的 next, 让 t 失效
                trail.nextWaiter = next;
          // 如果 next 是 null, 说明没有节点了,那么就可以将 trail 设置成最后一个节点
            if (next == null)
                lastWaiter = trail;
        }
        else
          // 如果该节点正常,那么就保存这个节点,在下次链接下个节点时使用
            trail = t;
      // 换下一个节点继续循环
        t = next;
    }
}
AQS.fullyRelease

释放锁,并唤醒AQS队列中的一个节点上的线程

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
      // 获取 state 变量
        int savedState = getState();
      // 如果释放成功,则返回 state 的大小,也就是之前持有锁的线程的数量
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
          // 如果释放失败,抛出异常
            throw new IllegalMonitorStateException();
        }
    } finally {
      //释放失败
        if (failed)
          // 将这个节点是指成取消状态.随后将从队列中移除.
            node.waitStatus = Node.CANCELLED;
    }
}
AQS.release

释放锁,并唤醒阻塞在锁上的线程

public final boolean release(int arg) {
  // 如果释放锁成功,返回 true, 可能会抛出监视器异常,即当前线程不是持有锁的线程.
	// 也可能是释放失败,但 fullyRelease 基本能够释放成功.
    if (tryRelease(arg)) {
      // 释放成功后, 唤醒 head 的下一个节点上的线程.
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
  // 释放失败
    return false;
}
ReentrantLock.Sync.tryRelease

释放锁:对state变量做减法,如果state变为0,则将持有锁的线程设置为null

查看tryRelease源码

AQS.isOnSyncQueue

判断是否在同步队列还是条件队列

final boolean isOnSyncQueue(Node node) {
  //node的waitStatus是否为CONDITION,或者它的prev域是否为空,如果是,那么它一定在条件队列上,返回false
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
  //如果node.next不为空,那么它一定在同步队列上
    if (node.next != null) // If has successor, it must be on queue
        return true;
  //如果从 tail 开始找上一个节点,找到了给定的节点,说明也在队列上.返回 true.
    return findNodeFromTail(node);
}
AQS.findNodeFromTail

从 tail 开始找上一个节点,找到了给定的节点,说明也在队列上

private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
AQS.ConditionObject.checkInterruptWhileWaiting
private int checkInterruptWhileWaiting(Node node) {
  // transferAfterCancelledWait >>>> 如果将 node 放入 AQS 队列失败,就返回 REINTERRUPT, 成功则返回 THROW_IE
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :0;
}
AQS.transferAfterCancelledWait

当线程中断了,就需要根据调用 transferAfterCancelledWait 方法的返回值来返回不同的常量

final boolean transferAfterCancelledWait(Node node) {
  // 将 node 的状态设置成 0 成功后,将这个 node 放进 AQS 队列中.
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
 		// 如果 CAS 失败, 返回 false ,
    // 当 node 不在 AQS 节点上, 就自旋. 直到 enq 方法完成.
    // JDK 认为, 在这个过程中, node 不在 AQS 队列上是少见的,也是暂时的.所以自旋.
    // 如果不自旋的话,后面的逻辑是无法继续执行的. 实际上,自旋是在等待在 signal 中执行 enq 方法让 node 入队.
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}
AQS.ConditionObject.reportInterruptAfterWait

这个方法可能会抛出异常,也可能会对当前线程打一个中断标记

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}
(4)signal方法源码
AQS.ConditionObject.signal
public final void signal() {
  // 如果当前线程不是持有该锁的线程.抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
  // 拿到 Condition 队列上第一个节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
AQS.ConditionObject.doSignal
private void doSignal(Node first) {
    do {
      // 如果第一个节点的下一个节点是 null, 那么, 最后一个节点也是 null.
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
      // 将 next 节点设置成 null.
        first.nextWaiter = null;
      // 通过从 First 向后找节点,直到唤醒或者没有节点为止.
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
AQS.transferForSignal
final boolean transferForSignal(Node node) {
  //如果不能改变状态,就取消这个 node. 
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
  // 将这个 node 放进 AQS 的队列,然后返回他的上一个节点.
    Node p = enq(node);
    int ws = p.waitStatus;
  // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞), 
    // 唤醒输入节点上的线程.
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
  // 如果成功修改了 node 的状态成0,就返回 true.
    return true;
}

9.LockSupport详解

(1)LockSupport是什么
  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
  • LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程。
(2)三种方式让线程等待与唤醒

方式1:使用Object中的wait()方法让线程等待,使用object中的notify()方法唤醒线程
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程(可以理解为方式1和方式2的加强版)

(3)wait、notify和await、signal的限制
  • synchronized -> wait->notify实现线程之间的等待唤醒
  • 调用顺序要先wait,再notify才能正常地实现线程之间的通知和唤醒。
  • wait和notify方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException。
  • 实现线程的等待和唤醒,Condition接口中的await和signal方法,同前面使用Object类中的wait和notify方法来实现,其效果大致相同。
  • 都必须要先等待(wait/await),再唤醒(notify/signal);等待和唤醒方法必须要在同步块或者方法里面且成对出现使用,否则会抛出java.lang.IllegalMonitorStateException
(4)LockSupport原理分析
  • LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法

  • LockSupport类使用了Permit(许可证)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零

  • 许可看成是一种(0和1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1

  • 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作,此外,park()+park(Object blocker) 可以用来阻塞当前线程阻塞传入的具体线程

  • park和unpark方法都需要调用Unsafe类中native修饰的方法,底层交由C语言去实现

  • permit默认是0,调用park()方法,当前线程th1就会阻塞,直到别的线程th2调用unpark(Thread thread),将当前线程th1的permit设置为1时(表示当前线程获取到许可),th1会被th2唤醒,然后会将permit再次设置为0并返回。

  • 调用unpark(thread)方法后,就会将th1线程的许可permit设置成1(注意多次调用unpark方法,不会累加,pemit值还是1),使得th1线程被唤醒。

(5)LockSupport使用案例
public class LockSupportTest {

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> {
            // 当前线程睡眠三秒钟,让thB先执行
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "coming in ....");
            LockSupport.park();
                /* 如果这里有两个LockSupport.park(),由于permit的值为1,
                上一行已经使用了permit,所以下一行被注释的打开会导致程序处于一直等待       的状态 * */
            //LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t" + "被唤醒了 ....");
        }, "thA");

        threadA.start();

        Thread threadB = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //如果有两个LockSupport.unpark(t1),由于permit的值最大为1,所以只能给park一个通行证
            //  LockSupport.unpark(threadA);
            LockSupport.unpark(threadA);
            System.out.println(Thread.currentThread().getName() + "\t" + "唤醒其他线程 ....");
        }, "thB");
        threadB.start();
    }
}

运行如上程序,即使不加锁,也不会出现异常 IllegalMonitorStateException,也不需要关心线程之间等待和唤醒的先后顺序。

(6)LockSupport总结
  • 没有使用LockSupport的等待唤醒通知机制必须synchronized里面执行wait和notify,在lock里面执行await和signal,这上面这两个都必须要持有锁才能干
  • LockSupport:俗称锁中断,LockSupport 解决了 synchronized 和 lock 的痛点
  • LockSupport不用持有锁块,不用加锁,程序性能好,无须注意唤醒和阻塞的先后顺序,不容易导致卡死

形象理解LockSupport:

  • 线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
  • 当调用park方法时:
    如果有凭证,则会直接消耗掉这个凭证然后正常退出
    如果无凭证,就必须阻塞等待凭证可用
  • 而unpark则相反,它会增加一个凭证,但凭证最多只能有1个,累加无放。
(7)LockSupport面试题
  • 为什么可以先唤醒线程后阻塞线程?

    因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。

  • 为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

    因为凭证的数量最多为1(不能累加),连续调用两次 unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

10.Semaphore详解

(1)什么是Semaphore

Semaphore是一个线程同步的辅助类,通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源,Semaphore常常被用作限流器,通过共享锁对资源进行限制。

(2)Semaphore使用场景

通常用于那些资源有明确访问数量限制的场景,常用于限流 。

比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

比如:停车场场景,车位数量有限,同时只能容纳多少台车,车位满了之后只有等里面的车离开停车场外面的车才可以进入。

(3)使用semaphore 实现停车场提示牌功能
/**
 * 使用Semaphore实现停车场提示牌功能
 */
public class TestCar_Semaphore {
    //停车场同时容纳的车辆10
    private static final Semaphore semaphore = new Semaphore(10, false);

    public static void main(String[] args) {
        //模拟100辆车进入停车场
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                try {
                    System.out.println("====" + Thread.currentThread().getName() + "来到停车场");
                    if (semaphore.availablePermits() == 0) {
                        System.out.println("车位不足,请耐心等待");
                    }
                    semaphore.acquire();//获取令牌尝试进入停车场
                    System.out.println(Thread.currentThread().getName() + "成功进入停车场");
                    Thread.sleep(new Random().nextInt(10000));//模拟车辆在停车场停留的时间
                    System.out.println(Thread.currentThread().getName() + "驶出停车场");
                    semaphore.release();//释放令牌,腾出停车场车位
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, i + "号车");
            thread.start();
        }

    }
}
(4)Semaphore构造方法
// 只传入一个int类型参数的构造方法采用的是非公平竞争资源的方式
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

// 传入一个int类型参数和一个boolean类型参数的构造方法可以指定竞争资源的方式是公平方式还是非公平方式
// 传入true代表是公平方式传入false代表是公平方式
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
(5)Semaphore.acquire源码分析
Semaphore.acquire
// 获取一个资源(许可),里面调用acquireSharedInterruptibly(int permits)方法
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
AQS.acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
  // 有中断就抛出中断异常
    if (Thread.interrupted())
        throw new InterruptedException();
  // 尝试获取共享资源,失败返回小于0的数
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
Semaphore.FairSync.tryAcquireShared

这个是内部类FairSync的成员方法,公平竞争资源

protected int tryAcquireShared(int acquires) {
    for (;;) {
       // 判断阻塞队列是否有等待者线程,有的话直接返回-1结束方法
        if (hasQueuedPredecessors())
            return -1;
      // 没有等待线程就阐释获取资源所,得到state值
        int available = getState();
      // 获取资源后state的值
        int remaining = available - acquires;
      // state<0说明获取资源失败,直接返回state的值(负数即失败)。否则CAS更新state的值并返回state
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

查看hasQueuedPredecessors源码

Semaphore.Sync.nonfairTryAcquireShared

这个是内部类Sync的成员方法,与Semaphore.FairSync.tryAcquireShared的唯一区别是:不用判断阻塞队列是否有等待者线程

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
AQS.doAcquireSharedInterruptibly

如果获取资源失败就执行该方法

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
          //如果当前节点是head结点的后继节点则当前线程可以继续尝试获取资源
            if (p == head) {
              //尝试获取锁
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                  //申请锁成功后,就将node移出queue,设置新head和共享传播(唤醒后继共享节点)
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

查看addWaiter源码

查看shouldParkAfterFailedAcquire源码

查看parkAndCheckInterrupt

产看.cancelAcquire源码

AQS.setHeadAndPropagate

node获取锁成功出队,设置新head,并将共享性传播给后继节点,即唤醒后继共享节点。为什么当一个节点的线程获取共享锁后,要唤醒后继共享节点?共享锁是可以多个线程共有的,当一个节点的线程获取共享锁后,必然要通知后继共享节点的线程,也可以获取锁了,这样就不会让其他等待的线程等很久,而传播性的目的也是尽快通知其他等待的线程尽快获取锁。

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
  //设置node为新head
    setHead(node);
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}
  • propagate > 0
    在ReentrantReadWriteLock中走到setHeadAndPropagate,只可能是propagate > 0,所以后面判断旧、新head的逻辑就被短路了。

而在Semaphore中走到setHeadAndPropagate,propagate是可以等于0的,表示没有剩余资源了,故propagate > 0不满足,往后判断。

  • h == null || h.waitStatus < 0
    首先判断旧head是否为null,一般情况下是不可能是等于null,除非旧head刚好被gc了。h == null不满足,继续判断h.waitStatus < 0,h.waitStatus可能等于0,可能等于-3。

    (1)h.waitStatus=0的情况,某个线程释放了锁(release or releaseShared)或者前一个节点获取共享锁传setHeadAndPropagate,唤醒后继节点的时候将h.waitStatus=-1设置为0。
    (2)h.waitStatus=-3,doReleaseShared唤醒head后继节点后h.waitStatus从-1到0,还没来得及更新head,即被唤醒的共享节点还没有setHeadAndPropagate,又有其他线程doReleaseShared唤醒head后继节点h.waitStatus从0到-3。

(6)Semaphore.release源码分析
Semaphore.release
public void release() {
    sync.releaseShared(1);
}
AQS.releaseShared
public final boolean releaseShared(int arg) {
  // 尝试释放资源,释放资源成功,调用doReleaseShared()方法
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
Semaphore.Sync.tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
    for (;;) {
      // 获取当前state的值
        int current = getState();
      // 得到释放资源后state的值
        int next = current + releases;
      // next < current说明传入参数是负数,非法,抛出异常
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
      // 尝试CAS更新state的值,更新成功返回true
        if (compareAndSetState(current, next))
            return true;
    }
}
AQS.doReleaseShared
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
          // 当前节点的状态是唤醒后继节点
            if (ws == Node.SIGNAL) {
              // 更新头结点状态为0(默认状态),更新失败自旋,更新成功唤醒后继结点
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
              // 唤醒head的后继节点
                unparkSuccessor(h);
            }
          //当头结点状态是0(默认状态),更新当前头结点状态为共享锁需要无条件传播,如果更新失败则自旋
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

查看unparkSuccessor源码

11.CountDownLatch简介

(1)CountDownLatch简介

CountDownLatch(闭锁)是一个同步协助类,允许一个线程或多个线程阻塞等待,直到其他线程完成操作后,被阻塞的线程才会被唤醒,然后执行后面的业务逻辑。

CountDownLatch的构造方法需要给定一个计数值(count)进行初始化。CountDownLatch简单理解就是一个倒计数器,调用它的await()会被阻塞,直到它的计数值(count)为0时,被阻塞的线程才会被唤醒,而调用它的countDown()方法会对计数器值减1,直到计数值为0后,之前被阻塞的线程都会被唤醒,同时后续再调用await()方法时就不会再被阻塞,因为计数值count是一次性的,当它的值减为0后就不会再变化了,所以后续调用await()方法时不会被阻塞,而是立即返回。

(2)使用场景
  • 多线程等待:并发线程同时执行

    下面的代码就模拟了远动员比赛的场景:

    三个远动员都阻塞在调用await()方法这里,当发号员准备两秒后发令,这是三个远动员同时往下进行

    public class CountDownLatchTest {
    
        public static void main(String[] args) {
            CountDownLatch countDownLatch = new CountDownLatch(1);
    
            for (int num = 0; num < 10; num++) {
                new Thread(() -> {
                    try {
                        //准备完毕,运动员都阻塞在这里,等待号令
                        countDownLatch.await();
                        System.out.println(Thread.currentThread().getName() + "开始跑……");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            // 发号员准备发令
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 发令枪:发令
            countDownLatch.countDown();
    
            //后续调用await方法将不会阻塞
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 单线程等待:并发线程完成后合并

    在有些场景中,并发任务前后存在依赖关系,比如数据详情页需要同时调用多个接口获取数据,并发请求获取到数据后,需要对结果进行合并。这就是典型的并发线程完成后需要合并的场景。

    public class CountDownLatchTest2 {
    
        public static void main(String[] args) {
            CountDownLatch countDownLatch = new CountDownLatch(3);
            for (int num = 0; num < 3; num++){
                new Thread(() ->{
                    try {
                        Thread.sleep(1000+ ThreadLocalRandom.current().nextInt(1000));
                        System.out.println(Thread.currentThread().getName() + "finish task");
    
                        countDownLatch.countDown();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
    
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("所有线程执行完成,对结果进行汇总");
        }
    }
    
(3)CountDownLatch.await源码
public void await() throws InterruptedException {
  //获取共享锁
    sync.acquireSharedInterruptibly(1);
}
AQS.acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
  //判断是否中断
    if (Thread.interrupted())
        throw new InterruptedException();
  //尝试获取共享锁,失败则执行doAcquireSharedInterruptibly
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
CountDownLatch.Sync.tryAcquireShared
protected int tryAcquireShared(int acquires) {
  //判断state是否等于0,等于0则返回1获取锁成功
    return (getState() == 0) ? 1 : -1;
}

查看AQS.doAcquireSharedInterruptibly源码

(4)CountDownLatch.countDown源码
//减少计数
public void countDown() {
    sync.releaseShared(1);
}
AQS.releaseShared
public final boolean releaseShared(int arg) {
  //尝试释放锁
    if (tryReleaseShared(arg)) {
      //如果释放锁成功则唤醒其他线程
        doReleaseShared();
        return true;
    }
    return false;
}
CountDownLatch.Sync.tryReleaseShared
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
      //获取state,state是CountDownLatch构造方法传进去的
        int c = getState();
      //state==0说明已经被其线程修改为0了,这里无事可干了
        if (c == 0)
            return false;
      //状态位预减1,注意这里是预减,因为多线程的情况下还要cas来保证原子性
        int nextc = c-1;
      //cas操作,c是预期值,nextc是改变值,如果在并发的情况下为false的就继续自旋,为true的就判断是否状态为0,为0就要执行唤醒等待线程
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

查看AQS.doReleaseShared源码

查看AQS.doReleaseShared源码

你可能感兴趣的:(Java面试,网络)