Concurrent programming-Condition underlying design

ConditionIt is a tool class for multi-thread coordination communication, which allows some threads to wait for a certain condition (condition) together. Only when the condition is met, the thread will be awakened. It is usually used together with (lock) for multiple Lockthreads Coordinate and synchronize access to shared resources. ConditionThe interface provides a mechanism similar to Object.wait()the and Object.notify()method, but it is more flexible and can control the thread more finely in the process of waiting and notifying. ConditionThe interface provides the following methods:

  • await(): Make the current thread wait until another thread calls signal()or signalAll()method to wake it up.
  • awaitUninterruptibly(): await()Similar to, but it will not return due to thread interruption.
  • signal(): Wake up a thread waiting on the condition, if there are multiple threads waiting, only wake up one of the threads.
  • signalAll(): Wake up all threads waiting on this condition.
  • awaitNanos(long nanosTimeout): Wait for the specified time, and return if not woken up before the timeout.

ConditionA typical use of is Lockto create multiple conditions on an object, each condition representing a different wait queue. A thread can wait for a specific condition to acquire a lock at a specific moment. For example, if multiple threads need to access a finite resource pool, you can create a condition for each resource pool and have the threads wait for the corresponding condition. When a resource is available, the corresponding condition can be used to notify the thread waiting for the resource so that it can acquire the resource.

Condition usage example

package org.example;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Example {
    private final Queue<Integer> queue = new LinkedList<>();
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    public void produce() {
        int i = 0;
        while (true) {
            lock.lock();
            i++;
            // 等待队列不满
            while (queue.size() == 3) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            // 生产一个元素
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            queue.add(i);
            System.out.println("Produced: " + i);
            // 唤醒一个等待的消费者线程
            condition.signal();
            lock.unlock();
        }
    }

    public void consume() {
        while (true) {
            // 等待队列不为空
            lock.lock();
            while (queue.isEmpty()) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            // 消费一个元素
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            int num = queue.remove();
            System.out.println("Consumed: " + num);
            // 唤醒一个等待的生产者线程
            condition.signal();
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Example example = new Example();
        Thread t = new Thread(example::produce);
        Thread t1 = new Thread(example::consume);
        t.start();
        t1.start();
    }

}

As shown in the above code, when there are more than 3 items in the queue, the thread of the producer will be blocked and cannot be produced again. At this time, the thread will be blocked, and then the consumer can seize the thread again at this time and start to consume

Condition source code analysis

await source code

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //创建了一个新节点数据结构还是一个链表(如下图所示)
    Node node = addConditionWaiter();
    //释放当前的锁并唤醒AQS队列中的一个线程
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //如果当前结点不在AQS队列中则阻塞当前线程
    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
    // 如果node 的下一个等待者不是null, 则进行清理Condition 队列上的节点
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

The most important code in the middle is whilethe loop. The judgment condition here is whether the current node is in the waiting queue. Under normal circumstances, it is definitely not, so at this time the current thread will be blocked waiting to be awakened

Condition node structure diagram

Condition(超高清).jpeg

addConditionWaiter

image.pngIt is very simple here to create a new node, and then maintain the node list, in which the canceled nodes will be cleaned up

fullyRelease
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)
            node.waitStatus = Node.CANCELLED;
    }
}
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 final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

就是将AQS的state置为0和exclusiveOwnerThread置为null,标识当前锁没有人占用然后就是unpark-AQS中的头节点就是队伍的第一个线程。

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

这里其实就是去除第一个节点然后再修改一下condition链表如下图所示 image.png

transferForSignal
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    //这里就是将结点的等待状态修改成初始状态0
    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).
     */
    //这里就是将等待的这个节点添加到AQS队列中
    Node p = enq(node);
    int ws = p.waitStatus;
    //这里将node的上一个结点的等待状态设置成SIGNAL
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //需要注意的是正常情况下是不在这里unpark的,而是放到AQS队列中等待unpark
        LockSupport.unpark(node.thread);
    return true;
}

这里很多人都会以为在这里执行了LockSupport.unpark(node.thread);所以唤醒了线程,其实不是,正常情况下还是要放到AQS队列中等待unpark,比如当前线程unlock了,如果刚好是当前线程可以抢占锁了此时就会unpark当前线程

private Node enq(final Node node) {
    for (;;) {
        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;
            }
        }
    }
}

Condition原理总结

就是在维护两个队列配合使用LockSupport.unpark(node.thread);LockSupport.park(node.thread);两个方法,具体逻辑图如下: Condition(超高清) (2).jpeg

Guess you like

Origin juejin.im/post/7233782463079006245