Condition

Condition接口

在并发编程中,每个Java对象都存在一组监视器方法,如wait()notify()以及notifyAll()方法,通过这些方法,我们可以实现线程间通信与协作(也称为等待唤醒机制),如生产者-消费者模式,而且这些方法必须配合着synchronized关键字使用。

与synchronized的等待唤醒机制相比,Condition具有更多的灵活性以及精确性,这是因为notify()在唤醒线程时是随机(同一个锁),而Condition则可通过多个Condition实例对象建立更加精细的线程控制,也就带来了更多灵活性:

  • 通过Condition能够精细的控制多线程的休眠与唤醒。

  • 对于一个锁,可以为多个线程间建立不同的Condition。

Condition是一个接口:

public interface Condition {

 /**
  * 使当前线程进入等待状态,直到【被通知(signal)】或【中断】
  * 当其他线程调用singal()或singalAll()方法时,该线程将被唤醒
  * 当其他线程调用interrupt()方法中断当前线程
  * await()相当于synchronized等待唤醒机制中的wait()方法
  */
 void await() throws InterruptedException;

 // 当前线程进入等待状态,直到被唤醒,该方法【不响应中断要求】
 void awaitUninterruptibly();

 // 调用该方法,当前线程进入等待状态,直到【被唤醒】或【被中断】或【超时】
 // 其中nanosTimeout指的等待超时时间,单位纳秒
 long awaitNanos(long nanosTimeout) throws InterruptedException;

 // 同awaitNanos,但可以指明时间单位
 boolean await(long time, TimeUnit unit) throws InterruptedException;

 // 调用该方法当前线程进入等待状态,直到被唤醒、中断或到达某个时间期限(deadline)
 // 如果没到指定时间就被唤醒,返回true,其他情况返回false
 boolean awaitUntil(Date deadline) throws InterruptedException;

 // 唤醒一个等待在Condition上的线程,
 // 该线程从等待方法返回前,必须获取与Condition相关联的锁,功能与notify()相同
 void signal();

 // 唤醒所有等待在Condition上的线程,
 // 该线程从等待方法返回前,必须获取与Condition相关联的锁,功能与notifyAll()相同
 void signalAll();
}

同步队列与等待队列

AQS中存在两种队列,一种是同步队列,一种是等待队列,而等待队列就相对于Condition而言的。在使用Condition前必须获得锁,同时在Condition的等待队列上的结点与前面同步队列的结点是同一个类即Node,其结点的waitStatus的值为CONDITION=1

同步队列等待队列的关系:

每个Condition都对应着一个等待队列,也就是说如果一个锁上创建了多个Condition对象,那么也就存在多个等待队列。等待队列是一个FIFO的队列,在队列中每一个节点都包含了一个线程的引用,而该线程就是Condition对象上等待的线程

当一个线程调用了await()相关的方法,那么该线程将会释放锁,并构建一个Node节点封装当前线程的相关信息,加入到等待队列中进行等待,直到被唤醒中断超时才从队列中移出。

Condition中的等待队列模型如下:

Node节点的数据结构,在等待队列中使用的变量与同步队列是不同的,Condtion中等待队列的结点,只有直接指向的后继结点,并没有指明前驱结点,而且使用的变量是nextWaiter而不是next。

等待队列中结点的状态只有两种即CANCELLEDCONDITION,前者表示线程已结束,需要从等待队列中移除,后者表示条件结点等待被唤醒

每个Codition对象对应于一个等待队列,也就是说AQS中只能存在一个同步队列,但可拥有多个等待队列

newCondition

public class ReentrantLock implements Lock, java.io.Serializable {
    public Condition newCondition() {
        // 使用自定义的条件
        return sync.newCondition();
    }
}

public class MyMutex implements Lock {
    private static class MySync extends AbstractQueuedSynchronizer {   
        /**
         * 主要用于等待/通知机制,每个condition都有一个与之对应的条件等待队列
         * @return condition
         */
        Condition newCondition() {
            return new ConditionObject();
        }
    }
}

public class ReentrantLock implements Lock, java.io.Serializable {
    final ConditionObject newCondition() {
        return new ConditionObject();
    }
}

AQS#ConditionObject

ConditionObject是Condition的实现类,该类就定义在了AQS中。在实现类ConditionObject中有两个结点,分别是firstWaiterlastWaiter,firstWaiter代表等待队列第一个等待结点,lastWaiter代表等待队列最后一个等待结点

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {
    
    public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

        public ConditionObject() { }
        // ...
    }
}

所以,只需要来看一下ConditionObject实现的await/signal方法来使用这两个成员变量就可以了。

await()

await()方法主要做了3件事:

  • 一是调用addConditionWaiter()方法将当前线程封装成node结点加入等待队列

  • 二是调用fullyRelease(node)方法释放同步状态,并唤醒后继结点的线程

  • 三是调用isOnSyncQueue(node)方法判断结点是否在同步队列中。注意是个while循环,如果同步队列中没有该结点就直接挂起该线程,需要明白的是如果线程被唤醒后就调用acquireQueued(node, savedState)执行自旋操作争取锁,即当前线程结点从等待队列转移到同步队列并开始努力获取锁

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {
    
    public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

        public ConditionObject() { }
        // ...
        
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter(); // 1.构建Node节点,并加入到等待队列中
            int savedState = fullyRelease(node); // 2.释放当前线程锁,即释放同步状态
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) { // 3.判断结点是否在同步队列(SyncQueue)中,即是否被唤醒
                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(); // 清理等待队列中不为CONDITION状态的结点
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
    }
}

执行addConditionWaiter()添加到等待队列:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {
    
    public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

        /**
         * Adds a new waiter to wait queue.
         * @return its new wait node
         */
        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) { // 判断是否为结束状态的结点并移除
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 新构建的节点的waitStatus是CONDITION,注意不是0或SIGNAL了
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            // 构建单向同步队列
            if (t == null)
                firstWaiter = node; // 加入等待队列
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
    }
}

为什么这里是单向队列,也没有使用CAS来保证加入队列的安全性呢?

因为await是Lock范式try中使用的,说明已经获取到锁了,所以就没必要使用CAS了。至于是单向,因为这里还不涉及到竞争锁,只是做一个条件等待队列。

线程已经按相应的条件加入到了条件等待队列中,那如何再尝试获取锁呢?

signal/signalAll

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {
    
    public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

        public final void signal() {
            if (!isHeldExclusively()) // 判断是否持有独占锁,如果不是抛出异常
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null) // 唤醒等待队列第一个结点的线程
                doSignal(first);
        }
        
        public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
    }
}

这里signal()方法做了两件事:

  • 一是判断当前线程是否持有独占锁,没有就抛出异常,从这点也可以看出只有独占模式先采用等待队列,而共享模式下是没有等待队列的,也就没法使用Condition。

  • 二是唤醒等待队列的第一个结点,即执行doSignal(first)

doSignal/doSignalAll

doSignal(first)方法中做了两件事:

  • 一是条件等待队列移除被唤醒的节点,然后重新维护条件等待队列的firstWaiter和lastWaiter的指向。

  • 二是将从等待队列移除的结点加入同步队列(在transferForSignal()方法中完成的),如果进入到同步队列失败,并且条件等待队列还有不为空的节点,则继续循环唤醒后续其他结点的线程

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer implements java.io.Serializable {
    
    public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

        private void doSignal(Node first) {
            do {
                // 移除条件等待队列中的第一个结点,
             	// 如果后继结点为null,那么说没有其他结点,将尾结点也设置为null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
             // 如果被通知节点没有进入到同步队列,并且条件等待队列还有不为空的节点,则继续循环通知后续结点
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
        
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            /*
             * 循环判断是否还有nextWaiter,
             * 如果有就像signal操作一样,将其从【条件等待队列】中移到【同步队列】中
             */
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }
    }

    final boolean transferForSignal(Node node) {
        // 尝试设置唤醒结点的waitStatus为0,即初始化状态
        // 如果设置失败,说明当前结点node的waitStatus已不为CONDITION状态,那么只能是结束状态了,因此返回false
        // 返回doSignal()方法中继续唤醒其他结点的线程,注意这里并不涉及并发问题,
        // 所以CAS操作失败只可能是预期值不为CONDITION,
    	// 而不是多线程设置导致预期值变化,毕竟操作该方法的线程是持有锁的。
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
	// 加入同步队列并返回前驱结点p
        Node p = enq(node);
        int ws = p.waitStatus;
        // 判断前驱结点是否为结束结点(CANCELLED=1)或者
        // 在设置前驱节点状态为Node.SIGNAL状态失败时,唤醒被通知节点代表的线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            // 唤醒node结点的线程
            LockSupport.unpark(node.thread);
        return true;
    }
}

总结:signal的唤醒过程

signal()被调用后,先判断当前线程是否持有独占锁,如果有,那么唤醒当前Condition对象中等待队列的第一个结点的线程,并从等待队列中移除该结点,移动到同步队列中:

  • 如果加入同步队列失败,那么继续循环唤醒等待队列中的其他结点的线程;

  • 如果成功加入同步队列,那么如果其前驱节点是已结束的节点或者设置前驱节点状态为Node.SIGNAL状态失败,则通过LockSupport.unpark()唤醒被通知节点代表的线程,到此signal()任务完成。

被唤醒后的线程,将从前面的await()方法中的while循环中退出,因为此时该线程的结点已在同步队列中,那么while (!isOnSyncQueue(node))将不在符合循环条件,进而调用AQS的acquireQueued()方法加入获取同步状态的竞争中,这就是等待唤醒机制的整个流程实现原理,流程如下图所示(注意无论是同步队列还是等待队列使用的Node数据结构都是同一个,不过是使用的内部变量不同罢了):

生产者-消费者Condition

package condition;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

import static java.lang.Thread.currentThread;
import static java.util.concurrent.ThreadLocalRandom.current;

public class TestCondition {
    // 定义显示锁
    private static final ReentrantLock lock = new ReentrantLock();
    // 创建与显式锁关联的Condition对象
    private static final Condition condition = lock.newCondition();
    // 链表
    private static final LinkedList<Long> list = new LinkedList<>();
    // 链表最大容量为100
    private static final int CAPACITY = 100;
    // 定义数据的初始值
    private static long i = 0;

    /**
     * 生产者方法
     */
    private static void produce() {
        // 获取锁
        lock.lock();

        try {
            // 当链表中数据量达到100时,生产者线程将被阻塞,加入与Condition关联的wait队列中
            while (list.size() >= CAPACITY) {
                condition.await();
            }

            // 当链表中数据量不足100时,生产新的数据
            i++;
            list.addLast(i);
            System.out.println(currentThread().getName() + " 生产了数据 " + i);
            // 1. 通知其他线程
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 消费者方法
     */
    private static void consume() {
        lock.lock();

        try {
            // 当list中数据为空时,消费者线程将被阻塞加入与Condition关联的wait队列
            while (list.isEmpty()) {
                condition.await();
            }

            // 消费数据
            Long value = list.removeFirst();
            System.out.println(currentThread().getName() + " 消费了数据 " + value);
            // 2.通知其他线程
            condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void sleep() {
        try {
            TimeUnit.SECONDS.sleep(current().nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        // 启动10个生产者线程
        IntStream.range(0, 10).forEach(i ->
                new Thread(
                        () -> {
                            for (; ; ) {
                                produce();
                                sleep();
                            }
                        }, "Producer-" + i
                ).start()
        );

        // 启动5个消费者线程
        IntStream.range(0, 5).forEach(i ->
                new Thread(
                        () -> {
                            for (; ; ) {
                                consume();
                                sleep();
                            }
                        }, "Consumer-" + i
                ).start()
        );
    }
}

输出结果:

...
Producer-6 生产了数据 8
Producer-4 生产了数据 9
Producer-8 生产了数据 10
Consumer-0 消费了数据 1
Consumer-1 消费了数据 2
Consumer-2 消费了数据 3
Consumer-4 消费了数据 4
Consumer-3 消费了数据 5
Producer-2 生产了数据 11
Producer-6 生产了数据 12
...

注释1和2处condition.signalAll()唤醒的是与Condition关联的阻塞队列中的所有阻塞线程。由于使用的是唯一的一个Condition实例,因此,生产者唤醒的有可能是与Condition关联的wait队列中的生产者线程。假设此时生产者线程被唤醒并抢到了CPU的调度获得了执行权,但又发现队列已满再次进入阻塞。这样的线程上下文开销实际上是没有意义的,甚至会影响性能(多线程下的线程上下文切换开销是非常大的性能损耗)。

因此,需要使用两个Condition对象,一个用于队列已满临界值条件的处理,另外一个用于队列为空的临界值条件的处理。此时,在生产者中唤醒的阻塞线程只能是消费者线程在消费者中唤醒的也只能是生产者线程

package condition;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;

import static java.lang.Thread.currentThread;
import static java.util.concurrent.ThreadLocalRandom.current;

public class TestCondition {
    // 定义显示锁
    private static final ReentrantLock lock = new ReentrantLock();
    // 创建与显式锁关联的Condition对象
    private static final Condition Full_Condition = lock.newCondition();
    private static final Condition EMPTY_Condition = lock.newCondition();
    // 链表
    private static final LinkedList<Long> list = new LinkedList<>();
    // 链表最大容量为100
    private static final int CAPACITY = 100;
    // 定义数据的初始值
    private static long i = 0;

    /**
     * 生产者方法
     */
    private static void produce() {
        // 获取锁
        lock.lock();

        try {
            // 当链表中数据量达到100时,生产者线程将被阻塞,加入Full_Condition wait队列中
            while (list.size() >= CAPACITY) {
                Full_Condition.await();
            }

            // 当链表中数据量不足100时,生产新的数据
            i++;
            list.addLast(i);
            System.out.println(currentThread().getName() + " 生产了数据 " + i);
            // 1. 生产者线程通知消费者线程
            EMPTY_Condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    /**
     * 消费者方法
     */
    private static void consume() {
        lock.lock();

        try {
            // 当list中数据为空时,消费者线程将被阻塞加入EMPTY_Condition wait队列
            while (list.isEmpty()) {
                EMPTY_Condition.await();
            }

            // 消费数据
            Long value = list.removeFirst();
            System.out.println(currentThread().getName() + " 消费了数据 " + value);
            // 2.消费者线程通知生产者线程
            Full_Condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void sleep() {
        try {
            TimeUnit.SECONDS.sleep(current().nextInt(5));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        // 启动10个生产者线程
        IntStream.range(0, 10).forEach(i ->
                new Thread(
                        () -> {
                            for (; ; ) {
                                produce();
                                sleep();
                            }
                        }, "Producer-" + i
                ).start()
        );

        // 启动5个消费者线程
        IntStream.range(0, 5).forEach(i ->
                new Thread(
                        () -> {
                            for (; ; ) {
                                consume();
                                sleep();
                            }
                        }, "Consumer-" + i
                ).start()
        );
    }
}

输出结果:

...
Producer-6 生产了数据 7
Producer-5 生产了数据 8
Producer-8 生产了数据 9
Producer-9 生产了数据 10
Consumer-0 消费了数据 1
Consumer-3 消费了数据 2
Consumer-4 消费了数据 3
Consumer-1 消费了数据 4
Consumer-2 消费了数据 5
Consumer-0 消费了数据 6
Producer-8 生产了数据 11
...
posted @ 2021-06-27 11:02  chenzufeng  阅读(403)  评论(0编辑  收藏  举报