Java并发 - AQS之ReentrantLock

news2024/10/17 3:36:36

文章目录

  • ReentrantLock
  • AQS 队列
    • AbstractOwnableSynchronizer
    • AbstractQueuedSynchronizer
    • Node
      • waitStatus
      • SHARED/EXCLUSIVE 模式
  • 加锁流程
    • 尝试加锁 tryAcquire
    • 加锁失败入队
      • addWaiter
      • enq
    • 阻塞等待 acquireQueued
      • parkAndCheckInterrupt
    • 放弃加锁 cancelAcquire
    • 唤醒阻塞线程 unparkSuccessor
  • 释放锁流程
  • 中断处理
    • 中断加锁模式 lockInterruptibly
  • Condition
    • ConditionObject
    • await
      • transferAfterCancelledWait
      • reportInterruptAfterWait
    • signal
    • signalAll
    • 对比Object#wait, notify/notifyAll

ReentrantLock

ReentrantLock 是 Java 中的一种可重入锁,属于 java.util.concurrent.locks 包。它提供了比传统的 synchronized 关键字更灵活的锁机制。以下是 ReentrantLock 的主要特点:

  1. 可重入性
    同一线程可以多次获得同一把锁,而不会导致死锁。当线程第一次获取锁时,锁的持有计数增加;每次释放锁时,计数减少,直到计数为零时,锁才真正被释放。
  2. 公平性
    ReentrantLock 可以选择公平性策略,默认非公平。公平锁按请求的顺序允许线程获取锁,而非公平锁则允许某些线程“插队”。可以通过构造方法指定锁的公平性:ReentrantLock lock = new ReentrantLock(true);
  3. 可中断的锁请求
    ReentrantLock 提供了lockInterruptibly()方法,允许线程在等待锁时响应中断。这使得线程在获取锁时可以被中断,增强了程序的灵活性。
  4. 条件变量支持
    ReentrantLock 提供了 Condition 类的支持,允许线程在特定条件下等待和通知。通过 newCondition() 方法可以创建条件变量,支持更加复杂的线程协调。
  5. 锁的状态查询
    可以通过isHeldByCurrentThread()方法检查当前线程是否持有锁,通过getHoldCount()方法获取当前线程持有锁的次数。
  6. 灵活的锁管理
    ReentrantLock 允许显式地尝试获取锁,使用 tryLock() 方法可以尝试立即获取锁而不阻塞。如果获取失败,可以选择执行其他操作。
  7. 性能优势
    在高竞争环境下,ReentrantLock 通常比 synchronized 更具性能优势,尤其是在高并发的场景中。
  8. 非阻塞锁请求
    tryLock(long timeout, TimeUnit unit)方法允许线程在指定的时间内尝试获取锁,如果无法在该时间内获取锁,则返回失败,避免了永久阻塞。
  9. 实现复杂的同步算法
    由于其灵活的特性,ReentrantLock 可以用于实现一些复杂的同步算法和数据结构。

AQS 队列

ReentrantLock 基于AQS同步机制实现加锁的机制

AbstractOwnableSynchronizer

java.util.concurrent.locks.AbstractOwnableSynchronizer是JUC里提供的一个抽象类,维护了当前获取锁的线程

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /**
     * The current owner of exclusive mode synchronization.
     */
    private transient Thread exclusiveOwnerThread;
}

一般使用的是其子类 AbstractQueuedSynchronizer,这个才是 AQS 本身

AbstractQueuedSynchronizer

java.util.concurrent.locks.AbstractQueuedSynchronizer 基于模板方法设计模式,

public abstract class AbstractQueuedSynchronizer
    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     */
    private volatile int state;
}

Node

Node是AbstractQueuedSynchronizer的一个内部类,表示AQS队列的节点对象

static final class Node {
	volatile int waitStatus;
	volatile Node prev;
	volatile Node next;
	volatile Thread thread;
	Node nextWaiter;
}

在这里插入图片描述

waitStatus

class Node {
   /** waitStatus value to indicate thread has cancelled */
   static final int CANCELLED =  1;
   /* waitStatus value to indicate successor's thread needs unparking */
   static final int SIGNAL    = -1;
   /** waitStatus value to indicate thread is waiting on condition */
   static final int CONDITION = -2;
   /**
    * waitStatus value to indicate the next acquireShared should
    * unconditionally propagate
    */
   static final int PROPAGATE = -3;
}

状态字段,仅采用以下值:

  1. SIGNAL:此节点的后继节点已(或即将)被阻止(通过停放),因此当前节点必须在释放或取消后继节点时取消停放。为避免竞争,获取方法必须首先指示它们需要信号,然后重试原子获取,然后在失败时阻止。
  2. CANCELLED:此节点由于超时或中断而被取消。节点永远不会离开此状态。特别是,具有已取消节点的线程永远不会再被阻止。
  3. CONDITION:此节点当前位于条件队列中。在传输之前,它将不会用作同步队列节点,此时状态将设置为 0。(此处使用此值与字段的其他用途无关,但简化了机制。)
  4. PROPAGATE:releaseShared 应传播到其他节点。这在 doReleaseShared 中设置(仅适用于头节点),以确保传播继续,即使其他操作已经介入。
  5. 0:以上都不是

为简化使用,这些值按数字排列。非负值表示节点不需要发信号。因此,大多数代码不需要检查特定值,只需检查与0的大小关系。对于普通同步节点,该字段初始化为 0,对于条件节点,该字段初始化为 CONDITION。使用 CAS(或在可能的情况下,使用无条件volatile写入)对其进行修改。

SHARED/EXCLUSIVE 模式

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;
}

加锁流程

非公平方式加锁流程如下

final void lock() {
	// 先尝试CAS加锁
    if (compareAndSetState(0, 1))
    	// 成功则
        setExclusiveOwnerThread(Thread.currentThread());
    else acquire(1);
}

公平方式加锁流程如下

final void lock() {
    acquire(1);
}

都是调用 acquire(1),这是AbstractQueuedSynchronizer的方法

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

尝试加锁 tryAcquire

公平和非公平的区别在于 tryAcquire 的各自的实现。tryAcquire 方法在AbstractQueuedSynchronizer中默认实现是直接抛异常

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

NonfairSync和FairSync中有各自的实现实现

// NonfairSync
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// Sync
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;
}

// FairSync
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;
}

重入次数限制

重入次数最大支持2147483647,在文档中有提到

在这里插入图片描述

加锁失败入队

加锁失败后,封装为一个Node进行等待队列

在这里插入图片描述

addWaiter

private Node addWaiter(Node mode) {
	// mode: Node.EXCLUSIVE
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
    	// 说明有线程在排队
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

如果有多个线程同时执行 addWaiter,那么多个线程可能node.prev = pred;都执行成功,如下图所示,此时pred.next = node;就需要进行并发控制了,这里采用的是 CAS。

在这里插入图片描述

enq

如果当前队列为空,没有线程排队或者前面并发情况下CAS失败的线程

private Node enq(final Node node) {
    for (;;) {
    	// 获取 tail 节点
        Node t = tail;
        if (t == null) { // Must initialize
            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();
            // p == head 表明该节点在队首,再次尝试获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 获取锁失败的线程: 阻塞该线程,等待被唤醒获取锁资源
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

获取失败后是否应该阻塞,只要是Node.SIGNAL的节点都需要阻塞

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	// 获取前驱节点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) { // CANCELLED
        // node.prev 指向队列中向前第一个不为 CANCELLED 的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {  // 0 | CONDITION -2 | PROPAGATE -3
        // 满足条件: <= 0 & != -1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

由前驱节点负责唤醒后一个节点

parkAndCheckInterrupt

中断会唤醒阻塞的线程

private final boolean parkAndCheckInterrupt() {
	// 阻塞当前线程
    LockSupport.park(this);
    // 返回线程是否被中断过
    return Thread.interrupted();
}

放弃加锁 cancelAcquire

cancelAcquire意为取消获取锁,什么时候取消获取,如下图所示,由变量 fasle 控制,但是如果加锁成功,failed 肯定为 false,所以不会执行cancelAcquire,而如果不成功那么会阻塞,或者是再次循环,所以也不会执行cancelAcquire。所以想要执行cancelAcquire要满足的条件只有1个,就是for循环里面操作抛了异常

在这里插入图片描述

下面来看取消获取锁的逻辑

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    if (node == null)
        return;
    node.thread = null;

    // 跳过取消的前驱节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)  // CANCELLED
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

唤醒阻塞线程 unparkSuccessor

唤醒指定节点的后继节点

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)  // 非 CANCELLED 状态
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) { // CANCELLED
        s = null;
        // 从后往前找到node的第一个waitStatus <= 0的后继节点
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread); // 解除阻塞状态
}

释放锁流程

如果当前线程持有锁,则会减少该线程的持有计数。每当线程成功获取锁时,持有计数会增加;释放锁时,计数会相应减少。

释放锁的条件

  1. 持有计数为零:当持有计数减小到零时,表示当前线程完全释放了锁,此时会进行锁的状态更新。
  2. 唤醒等待线程:如果有其他线程在等待获取该锁,ReentrantLock 会唤醒等待队列中的一个或多个线程,以便它们可以尝试获取锁。
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // 存在等待队列
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

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;
}

因此非公平锁和公平锁释放锁的流程是一样的

线程可以反复加锁,但也需要释放同样加锁次数的锁,即重入了多少次,就要释放多少次,不然也会导致锁不被释放。什么情况下会重入呢?比如下面这个:

public void m() {
    for (int i = 0; i < 10; i++) {
        lock.lock();
    }
    try {
        // ... method body
    } finally {
        lock.unlock(); // 只释放一次,不会导致锁释放
    }
}

在这里插入图片描述

中断处理

因为 acquireQueued 方法如果返回,那必然是获取锁成功,但是从入队到获取锁成功这段时间内线程是可能被中断的,中断需要由调用方进行处理。

在这里插入图片描述

acquireQueued 在获取锁失败进行阻塞时,会清除中断标志位

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted(); // 调用 currentThread().isInterrupted(true)
}

所以框架在此时中断当前线程,重置中断标志位,以让线程自己处理中断

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

调用interrupt()方法会将当前线程的中断状态设置为 true。这表示该线程希望被中断。该状态可以在其他地方通过Thread.interrupted()isInterrupted()方法检查。

如果当前线程正在执行某些阻塞操作,如Object.wait()Thread.sleep()BlockingQueue.take()等,调用interrupt()会导致这些操作抛出 InterruptedException。
这使得线程能够在等待或睡眠状态中被唤醒,以便做出适当的响应。

为什么要调用currentThread().isInterrupted(true)清除中断标志后然后又通过Thread.currentThread().interrupt()重置中断状态?

中断加锁模式 lockInterruptibly

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

private void doAcquireInterruptibly(int arg) throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                // 如果有中断过,直接抛出中断异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Condition

下面这个示例使用Condition:

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

class BoundedBuffer {
    private final int[] buffer;
    private int count, putIndex, takeIndex;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();
    private final Condition notFull = lock.newCondition();

    public BoundedBuffer(int size) {
        buffer = new int[size];
    }

    public void put(int value) throws InterruptedException {
        lock.lock();
        try {
            while (count == buffer.length) {
                notFull.await(); // 等待缓冲区不满
            }
            buffer[putIndex] = value;
            if (++putIndex == buffer.length) putIndex = 0; // 循环使用
            count++;
            notEmpty.signal(); // 唤醒等待取数据的线程
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await(); // 等待缓冲区不空
            }
            int value = buffer[takeIndex];
            if (++takeIndex == buffer.length) takeIndex = 0; // 循环使用
            count--;
            notFull.signal(); // 唤醒等待放数据的线程
            return value;
        } finally {
            lock.unlock();
        }
    }
}

ConditionObject

java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject

内部维护了一个单向链表

public class ConditionObject implements Condition {
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
}

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);
        // checkInterruptWhileWaiting 调用的是
        // Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
        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 方法用于将等待的节点加入链表尾部

private Node addConditionWaiter() {
     Node t = lastWaiter;
     // If lastWaiter is cancelled, clean out.
     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;
 }

删除所有取消的节点,其实就是链表操作,删除t.waitStatus != Node.CONDITION的节点

private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            // 头节点
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        } else
            trail = t;
        // 下一个节点
        t = next;
    }
}

调用 AQS 释放锁

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
        	// 必然释放锁失败,也就是抛出 IllegalMonitorStateException 异常
            node.waitStatus = Node.CANCELLED;
    }
}

判断是否在AQS同步队列中

final boolean isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    return findNodeFromTail(node);
}

// 从尾节点开始找
private boolean findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}

transferAfterCancelledWait

final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin.
     */
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

reportInterruptAfterWait

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        selfInterrupt();
}

signal

该方法用于唤醒一个在该条件上等待的线程。如果有多个线程在等待,signal 方法只会唤醒其中一个线程。

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

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

将等待状态由Node.CONDITION改为0,然后进入AQS同步队列

final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    // CANCELLED
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

signalAll

该方法用于唤醒所有在该条件上等待的线程。对每个Node都调用transferForSignal方法

public final void signalAll() {
    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;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

对比Object#wait, notify/notifyAll

Condition 和 Object#wait 都用于实现线程间的协调与通信,但它们在设计、使用方式和功能上有一些重要的区别。以下是它们之间的比较:

  1. 基本概念
    Object#wait是 Java 的内置方法,任何对象都可以调用。线程在调用 wait() 方法时会释放该对象的监视器锁,并进入等待状态,直到被其他线程调用 notify() 或 notifyAll() 唤醒。
    Condition是 java.util.concurrent.locks 包中的一个接口,通常与 ReentrantLock 一起使用。提供了更灵活的线程间通信机制,可以在条件不满足时阻塞线程,并在条件满足时唤醒。

  2. 使用方式
    Object#wait需要配合synchronized使用,线程必须持有对象的监视器锁才能调用 wait(),否则会抛出IllegalMonitorStateException。唤醒等待的线程时,必须在同一个对象的锁上调用 notify() 或 notifyAll()。而Condition需要与 ReentrantLock 结合使用,调用 lock() 方法获取锁后,才能调用 await()。唤醒等待的线程时,可以使用 signal() 或 signalAll(),不需要在同一个对象上。

synchronized (obj) {
    obj.wait(); // 进入等待状态
    // 处理逻辑
}

lock.lock();
try {
    condition.await(); // 进入等待状态
    // 处理逻辑
} finally {
    lock.unlock();
}
  1. 灵活性:Object#wait只能在对象的监视器锁上使用,缺乏灵活性。Condition支持多个条件变量,允许在同一个锁下定义多个不同的条件。更适合复杂线程协调需求
  2. 性能:Object#wait在高并发场景下,性能可能较低,因为synchronized会导致线程竞争和上下文切换。Condition通常在高并发环境中性能更优,因为它支持更细粒度的控制,可以避免不必要的上下文切换。
  3. 可读性和维护性
    Object#wait代码可读性较低,容易导致死锁和复杂的错误。Condition提供更好的可读性和维护性,可以更清晰地表达线程之间的关系和条件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大语言模型训练

大语言模型训练 1.两大问题2.并行训练2.1数据并行2.2模型并行2.3张量并行2.4混合并行 3.权重计算3.1浮点数3.2混合精度训练3.3deepspeed&#xff08;微软&#xff09;3.3.1 ZeRO3.3.2ZeRO-offload 3.3总结 4.PEFT4.1Prompt TuningPrefix-tuning4.2P-tuning & P-tuning v2 5…

arcpy总结

arcpy 一、是什么二、为什么三、怎么用1、在哪里打开2、基础术语3、代码组织4、案例&#xff08;1&#xff09;裁剪&#xff08;2&#xff09;土地变化特征分析&#xff08;4&#xff09;文件访问与检测&#xff08;5&#xff09;空间数据的查询、插入与更新&#xff08;6&…

伯努利分布(Bernoulli distribution)的两次成功之间间隔次数的分布

伯努利分布&#xff08;Bernoulli distribution&#xff09;是一种特殊的二项式分布&#xff0c;即0-1分布。百科上已经说明了这种分布&#xff0c;即&#xff0c;其中。其数学期望为&#xff0c;方差为。详细说明见0—1分布_百度百科 本文进一步说明对于这类分布的事件&#…

BUUCTF-greatescape1

发现有ftp包和tcp包居多 下载解压是个流量包&#xff0c;使用wiresharh打开&#xff0c;CTRLF&#xff0c;按下图搜索ftp tcp18流发现ssc.key 传送&#xff0c;在19流发现key内容 复制保存为ssc.key, 加载key解密tls&#xff0c;再追踪tls流可得flag INS{OkThatWasWay2Easy} …

微知-Bluefield DPU使用flint烧录固件报错MFE_NO_FLASH_DETECTED是什么?MFE是什么?

文章目录 背景一些报错场景MFE是什么&#xff1f;有哪些MFE 背景 在DPU的fw操作flint的时候&#xff0c;很多命令都会报这个错误&#xff1a;MFE_NO_FLASH_DETECTED&#xff0c;早期很疑惑并且猜测MFE是Mellanox Firmware Engine。实际并不是&#xff0c;具体还得走到mellanox…

linux 中mysql my.cnf 配置模版

前置准备 sudo systemctl stop mysqld 注意&#xff1a; 原本配置重命名做备份 备份数据 删文件 直接新建 my.cnf 把配置 11要粘进去的内容 直接粘进去 注意&#xff1a;尽管log-bin 和 log_bin 都可以启用二进制日志&#xff0c;但为了保持与现代MySQL版本的兼容性和一…

【算法系列-哈希表】两数之和(Map)

【算法系列-哈希表】两数之和 (Map) 文章目录 【算法系列-哈希表】两数之和 (Map)1. 两数之和(LeetCode 1, 梦开始的地方)1.1 思路分析&#x1f3af;1.2 解题过程&#x1f3ac;1.3 代码示例&#x1f330; 2. 四数相加II(LeetCode 454)2.1 思路分析&#x1f3af;2.2 解题过程&am…

MySQL-08.DDL-表结构操作-创建-案例

一.MySQL创建表的方式 1.首先根据需求文档定义出原型字段&#xff0c;即从需求文档中可以直接设计出来的字段 2.再在原型字段的基础上加上一些基础字段&#xff0c;构成整个表结构的设计 我们采用基于图形化界面的方式来创建表结构 二.案例 原型字段 各字段设计如下&…

rsync 数据镜像同步服务笔记

1. rsync概述 定义&#xff1a;rsync是一款数据镜像备份工具&#xff0c;支持快速完全备份和增量备份&#xff0c;支持本地复制与远程同步。镜像指两份完全相同的数据备份.特点&#xff1a; 支持整个目录树和文件系统的更新&#xff1b;可选择性地保留符号链接、文件属性、权限…

【Orange Pi 5嵌入式应用编程】-BMP280传感器驱动

BMP280传感器驱动 文章目录 BMP280传感器驱动1、BMP280传感器介绍2、BMP280的测量流程2.1 气压测量2.2 温度测量2.3 IIR滤波2.4 滤波器选择2.5 噪声3、BMP280的功耗模式3.1 休眠模式3.2 强制模式3.3 正常模式3.4 模式转换4、数据读取及计算4.1 寄存器数据覆盖4.2 输出补偿4.3 补…

一键生成证件照的开源利器:HivisionIDPhotos使用教程

HivisionIDPhotos使用教程&#xff1a;一键生成证件照的开源利器 HivisionIDPhotos 是一款开源的、轻量级且高效的AI工具&#xff0c;专注于证件照的自动生成。通过这一工具&#xff0c;用户只需上传一张自拍或其他照片&#xff0c;便能快速生成标准尺寸的证件照&#xff0c;免…

跟李沐学AI:Transformer

Transformer架构 (图源:10.7. Transformer — 动手学深度学习 2.0.0 documentation) 基于编码器-解码器架构来处理序列对 与使用注意力的seq2seq不同&#xff0c;Transformer纯基于注意力 多头注意力&#xff08;Multi-Head Attention&#xff09; (图源:10.5. 多头注意力 …

MyBatis环境配置详细过程

在此之前我们需要知道Maven和依赖是什么。 什么是Maven&#xff1f; Maven 是一个项目管理和构建自动化工具&#xff0c;最初由Apache软件基金会开发&#xff0c;主要用于Java项目的构建、依赖管理、文档生成和发布。Maven使用一种基于XML的配置文件&#xff08;pom.xml&…

vue后台管理系统从0到1(6)引入pinia实现折叠功能

文章目录 vue后台管理系统从0到1&#xff08;6&#xff09;引入pinia实现折叠功能分析&#xff1a;安装并使用 pinia vue后台管理系统从0到1&#xff08;6&#xff09;引入pinia实现折叠功能 分析&#xff1a; 首先&#xff0c;接着上一期&#xff0c;我们项目启动起来应该是…

【算法思想·二叉树】用「遍历」思维解题 II

本文参考labuladongsuanfa笔记[【强化练习】用「遍历」思维解题 II | labuladong 的算法笔记] 如果让你在二叉树中的某些节点上做文章&#xff0c;一般来说也可以直接用遍历的思维模式。 270. 最接近的二叉搜索树值 | 力扣 | LeetCode | 给你二叉搜索树的根节点 root 和一个目…

通信工程学习:什么是SDRAM同步动态随机存取存储器

SDRAM&#xff1a;同步动态随机存取存储器 SDRAM&#xff0c;全称为Synchronous Dynamic Random Access Memory&#xff0c;即同步动态随机存取存储器&#xff0c;是一种广泛应用于计算机和嵌入式系统中的内存技术。以下是对SDRAM的详细介绍&#xff1a; 一、SDRAM的定义与特点…

TimeGen3.2

一、安装 1.安装包下载 软件安装压缩包&#xff0c;点击链接下载&#xff0c;自取。 链接: https://pan.baidu.com/s/1kebJ2z8YPMhqyvDiHLKktw?pwd0000 提取码: 0000 二、解压安装 1.解压 2.安装软件 &#xff08;1&#xff09;双击timegen-pro-3.2.exe文件 &#xff…

[CTF夺旗赛] CTFshow Web13-14 详细过程保姆级教程~

前言 ​ CTFShow通常是指网络安全领域中的“Capture The Flag”(夺旗赛)展示工具或平台。这是一种用于分享、学习和展示信息安全竞赛中获取的信息、漏洞利用技巧以及解题思路的在线社区或软件。参与者会在比赛中收集“flag”&#xff0c;通常是隐藏在网络环境中的数据或密码形…

SHCTF-2024-week1-wp

文章目录 SHCTF 2024 week1 wpMisc[Week1]真真假假?遮遮掩掩![Week1]拜师之旅①[Week1]Rasterizing Traffic[Week1]有WiFi干嘛不用呢&#xff1f; web[Week1] 单身十八年的手速[Week1] MD5 Master[Week1] ez_gittt[Week1] jvav[Week1] poppopop[Week1] 蛐蛐?蛐蛐! SHCTF 2024…

一些自定义函数

目录 一.strcmp()函数 二.strstr()函数 三.memcpy函数 四.memmove函数 五.strncpy函数 六.strcat函数 七.atoi函数 八.strlen函数 一.strcmp()函数 strcmp 函数是用于比较两个字符串是否相等的函数。它通过逐个字符比较两个字符串的 ASCII 值&#xff0c;来判断它们的相…