Bootstrap

AQS的同步队列和条件队列原理

二者区别

首先,AQS中的 同步队列 和 条件队列 是两种不同队列:

  • 目的不同:同步队列主要用于实现锁机制(也就是锁的获取和释放),而条件队列用于实现条件变量,条件变量是并发编程中一种用于线程间通信的机制,它允许一个或多个线程在特定条件成立之前等待并释放相关的锁,直到其他线程改变了条件并显式的唤醒等待在该条件中的线程。比较典型的一个条件队列使用场景就是 ReentrantLock 的 Condition。
  • 使用方式不同:同步队列是AQS自动管理的,开发者通常不需要直接与之交互;而条件队列是通过Condition接口暴露给开发者的,需要显示地调用 等待await()方法 和 通知signal()/signalAll()方法
  • 队列类型不同:虽然它们都是队列结构,但同步队列是所有基于AQS同步器共享的,每个同步器实例只有一个同步队列;但条件队列是每个 Condition实例特有的,一个同步器可以有多个 Condition对象,因此也就有多个条件队列。

实现原理

在这里插入图片描述

同步队列

同步队列的实现原理比较简单,AQS中同步队列是一个FIFO队列,节点类型为AQS的内部类Node。Node有两个指针,prev和next,分别指向前置节点和后置节点,一个个Node节点组成了一个双向链表。

  1. 当一个线程尝试获取锁失败时,它会被封装成一个Node节点加入到队列尾部。
  2. 这个节点会处于等待状态,直到锁被其他线程释放。
  3. 当锁被释放时,队列的头节点(持有锁的线程)会通知其后继节点(如果存在的话),后继节点尝试获取锁。这个过程会一直持续,直到队列为空。

原码-内部类Node

static final class Node {
     	// 前置节点和后置节点构成双向链表
        volatile Node prev;
        volatile Node next;
        // 线程本身
        volatile Thread thread;
        // 状态信息,标识节点在同步队列中的等待状态
        volatile int waitStatus;
}

源码-获取锁

public final void acquire(int arg) {
	// 尝试获取锁失败之后,将线程封装成一个Node节点加入队列
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
	}
}

private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	// 首先尝试直接在尾部插入节点
	Node pred = tail;
	if (pred != null) {
	    node.prev = pred;
	    if (compareAndSetTail(pred, node)) {
	        pred.next = node;
	        return node;
	    }
	}
	// 插入失败时,进入完整的入队操作
	enq(node);
	return node;
}

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // 队列为空,初始化(第一个节点为虚节点,也叫哨兵节点,并不存储任何信息,只是占位。真正的第一个有数据的节点,是从第二个节点开始的。)
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued() 中方法链路太长,这里拆开讲解。

// 在加入队列排队的同时尝试抢夺资源
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
      	// 自旋
        for (;;) {
          	// 获得当前节点的前置节点,判断当前节点是否是头节点。若是则进行抢占锁
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
              	// 若抢占成功,则设置当前节点为头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
          	// 修改Node节点状态,使其线程等待。等候唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
              	// 线程被唤醒,进行自旋判断抢占锁
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
// 判断前置节点的 waitStatus 是 SIGNAL 状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
  	// 获取前置节点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
    		// 如果是 SIGNAL 状态,即等待被占用的资源释放,直接返回true
        return true;
    if (ws > 0) {
       	// 如果 ws 大于0说明是 CANCELLED 状态
        do {
        		// 循环判断前置节点的前置节点是否也为 CANCELLED 状态,忽略该状态的节点,重新连接队列
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
       	// 将当前节点的前置节点设置为 SIGNAL 状态,用于后续唤醒操作
      	// 程序第一次执行到这返回为false,还会进行外层第二次循环
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 挂起线程,等候唤醒
private final boolean parkAndCheckInterrupt() {
  	// 线程挂起,程序不会继续向下执行
    LockSupport.park(this);
    return Thread.interrupted();
}

源码-释放锁

// 尝试释放同步状态。
public final boolean release(int arg) {
	 // 尝试释放同步状态
    if (tryRelease(arg)) {
    	// 获取当前同步队列的头节点
        Node h = head;
        // 检查头节点是否存在且其 waitStatus 不为 0(不为 0 表示该节点处于等待状态或取消状态)
        if (h != null && h.waitStatus != 0)
        	// 唤醒头节点的后继节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 唤醒指定节点的后继节点。
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); // 清除节点的状态

    Node s = node.next; // 获取当前节点的后继节点
    if (s == null || s.waitStatus > 0) { // 如果后继节点为 null 或者其状态大于 0(则为1,已取消)
        s = null; 
        // 从尾部向前遍历找到实际未被取消的后继节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0) // 找到未被取消的节点
                s = t; // 设置为后继节点
    }
    if (s != null) 
        LockSupport.unpark(s.thread); // 唤醒后继节点对应的线程
}

条件队列

条件队列与同步队列不同,它通过AQS 中内部类 ConditionObject 实现 Condition 接口,它有两个属性,分别是 firstWaiter(队列的头节点)和 lastWaiter(队列的尾节点),节点类型依然是AQS的内部类Node,通过内部类Node中的 nextWaiter 指针指向下一个等待条件的节点,这样组成一个单向链表。

  1. 当线程调用了 Condition 的 await() 方法后,它会释放当前持有的锁,并且该线程会被加入到条件队列中等待。
  2. 然后线程会处于等待状态,直到其他线程调用 signal() 或者 signalAll() 方法来通知条件已经满足。
    a. signal() 唤醒等待队列中的头节点对应的线程
    b. signalAll() 唤醒队列中所有等待的线程

原码-内部类Node

static final class Node {
        // 线程本身
        volatile Thread thread;
        // 状态信息,标识节点在同步队列中的等待状态
        volatile int waitStatus;
        // 下一个等待条件的节点
        Node nextWaiter;
}

源码-等待

public final void await() throws InterruptedException {
    // 如果线程已经被中断,则抛出 InterruptedException
    if (Thread.interrupted())
        throw new InterruptedException();

    // 将当前线程添加到条件队列中
    Node node = addConditionWaiter();

    // 释放当前线程所持有的锁,并返回释放前的状态,以便以后可以重新获取到相同数量的锁
    int savedState = fullyRelease(node);

    // 中断模式,用于记录线程在等待过程中是否被中断
    int interruptMode = 0;

    // 如果当前节点不在同步队列中,则表示线程应该继续等待
    while (!isOnSyncQueue(node)) {
        // 阻塞当前线程,直到被唤醒或中断
        LockSupport.park(this);

        // 检查线程是否被中断,并更新 interruptMode 状态
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }

    // 当前节点成功加入到同步队列后,尝试以中断模式获取锁
    // 如果在此过程中线程被中断,且不是在 signal 之后,则设置中断模式为 REINTERRUPT
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;

    // 如果当前节点后面还有等待的节点,从等待队列中清理掉被取消的节点
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();

    // 根据中断模式处理中断
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

// 将当前线程添加到条件队列中
private Node addConditionWaiter() {
    // 获取最后一个等待者
    Node t = lastWaiter;

    // 如果最后一个等待者已被取消,则清理已取消的等待者
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters(); // 清理已取消的等待者
        t = lastWaiter; // 更新最后一个等待者
    }

    // 创建一个新的条件等待节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);

    // 如果条件队列为空,则将新节点设置为第一个等待者
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node; // 否则将新节点添加到最后一个等待者的后面

    // 更新最后一个等待者为新节点
    lastWaiter = node;

    // 返回新创建的条件等待节点
    return node;
}

源码-唤醒

signal(),唤醒等待队列中的头节点对应的线程

public final void signal() {
    // 如果当前线程没有独占锁,则抛出 IllegalMonitorStateException
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();

    // 获取条件队列中的第一个等待者
    Node first = firstWaiter;

    // 如果存在第一个等待者,则将其移动到同步队列中
    if (first != null)
        doSignal(first);
}

private void doSignal(Node first) {
    do {
        // 如果条件队列的第一个等待节点的 nextWaiter 为 null,则更新 firstWaiter 和 lastWaiter
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;

        // 清除第一个等待节点的 nextWaiter
        first.nextWaiter = null;

    } while (!transferForSignal(first) && // 尝试将节点转移到同步队列中并尝试唤醒它
             (first = firstWaiter) != null); // 如果还有等待节点,则继续处理
}

// 将指定的条件等待节点从条件队列转移到同步队列中,并尝试唤醒它。
final boolean transferForSignal(Node node) {
    // 如果无法更改 waitStatus,则说明节点已被取消
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    // 将节点插入到同步队列中,并尝试设置前驱节点的 waitStatus 以指示当前线程正在等待
    Node p = enq(node);
    int ws = p.waitStatus;

    // 如果前驱节点的 waitStatus 大于 0 或者设置 waitStatus 失败,则唤醒当前线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);

    // 返回 true 表示转移成功
    return true;
}


signalAll(),唤醒等待队列中所有的线程

public final void signalAll() {
    // 如果当前线程没有独占锁,则抛出 IllegalMonitorStateException
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();

    // 获取条件队列中的第一个等待者
    Node first = firstWaiter;

    // 如果存在第一个等待者,则将所有等待者移动到同步队列中
    if (first != null)
        doSignalAll(first);
}

private void doSignalAll(Node first) {
    // 清空条件队列的头部和尾部指针
    lastWaiter = firstWaiter = null;

    // 循环处理条件队列中的所有等待节点
    do {
        // 获取当前节点的下一个等待节点
        Node next = first.nextWaiter;

        // 清除当前节点的 nextWaiter
        first.nextWaiter = null;

        // 将当前节点转移到同步队列中,并尝试唤醒它
        transferForSignal(first);

        // 更新当前节点为下一个等待节点
        first = next;
    } while (first != null); // 继续处理直到条件队列为空
}

Demo-比较典型的条件队列使用场景

小编编写了一个比较典型的条件队列使用场景 ReentrantLock 的 Condition,大家可以通过debug该Demo查看源码,便于理解。

package com.gw.tool.demos.concurrent;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Description: ReentrantLock.Condition使用场景Demo
 *
 * @author YanAn
 * @date 2023/8/10 13:52
 */
public class ReentrantLockDemo {

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();

    public void awaitAndSignal() throws InterruptedException {
        lock.lock();
        try {
            // 等待条件唤醒
            System.out.println(Thread.currentThread().getName() + ",等待条件1唤醒");
            condition1.await();
            System.out.println(Thread.currentThread().getName() + ",满足条件1被唤醒");
            System.out.println(Thread.currentThread().getName() + ",等待条件2唤醒");
            condition2.await();
            System.out.println(Thread.currentThread().getName() + ",满足条件1被唤醒");
        } finally {
            lock.unlock();
        }
    }

    public void signalCondition1() throws InterruptedException {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ",唤醒等待条件1的线程");
            condition1.signal(); // 唤醒等待的线程
        } finally {
            lock.unlock();
        }
    }

    public void signalCondition2() throws InterruptedException {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ",唤醒等待条件2的线程");
            condition2.signal(); // 唤醒等待的线程
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo demo = new ReentrantLockDemo();

        Thread threadA = new Thread(() -> {
            try {
                demo.awaitAndSignal();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread threadB = new Thread(() -> {
            try {
                demo.signalCondition1();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread threadC = new Thread(() -> {
            try {
                demo.signalCondition2();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        threadA.start();
        Thread.sleep(100); // 确保线程A先运行
        threadB.start();
        Thread.sleep(100);
        threadC.start();
    }
}

;