Condition 详解

Condition

任何一个Java 对象,都拥有一组监视器方法(定义在java.lang.Object 上),主要包括wait(),wait(long timeOut),notify()以及notifyAll()方法。这些方法与synchronized 同步关键字配合,可以实现等待/通知模式。Condition 接口也提供了类似Object 的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式已经功能特征上还是有差别的。
通过对比Object的监视器方法和Condition接口,可以更详细地了解Condition的特性,对比项与结果如下表。
在这里插入图片描述

Condition 使用

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

public class BoundedQueue<T> {
    // 线程池
    private static ExecutorService THREAD_POOL = new ThreadPoolExecutor(2, 4, 60, TimeUnit.MINUTES,
            new LinkedBlockingQueue<Runnable>(1000), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    private Object[] items; // 对象数组
    // 添加的下标,删除的下标和数组当前数量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock(); // 定义一个可重入锁
    private Condition notEmpty = lock.newCondition(); // 添加一个Condition
    private Condition notFull = lock.newCondition(); // 添加一个Condition

    public BoundedQueue(int size) {
        items = new Object[size];
    }

    /**
     * 添加一个元素,如果数组满,则添加线程进入等待状态,直到有"空位"
     * @param t
     * @throws InterruptedException
     */
    public void add(T t) throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (count == items.length) { // 如果数组满了,notFull进入等待
                System.out.println("items满了,add方法进入等待.");
                notFull.await(); // 等待remove方法里的notFull.signal()
            }

            items[addIndex] = t; // item添加对象
            if (++addIndex == items.length) // 调整数组索引,避免越界
                addIndex = 0;
            ++count; // count+1,代表添加了一个对象
            notEmpty.signal(); // 走到这里,数组里至少有1个对象,必不为空,因此唤醒notEmpty
        } finally {
            System.out.println("add: " + t);
            lock.unlock(); // 释放锁
        }
    }

    /**
     * 由头部删除一个元素,如果数组空,则删除线程进入等待状态,
     * 直到有新添加元素(注意这里并没有真的删除元素,只是把count-1当作是删除)
     * @return
     * @throws InterruptedException
     */
    @SuppressWarnings("unchecked")
    public T remove() throws InterruptedException {
        lock.lock(); // 获取锁
        try {
            while (count == 0) { // 如果数组为空,notEmpty进入等待
                System.out.println("items为空,remove方法进入等待.");
                notEmpty.await(); // 等待add方法里的notEmpty.signal()
            }

            Object x = items[removeIndex]; // item移除对象(假移除)
            if (++removeIndex == items.length) // 调整数组索引,避免越界
                removeIndex = 0;
            --count; // count-1,代表移除了一个对象
            notFull.signal(); // 走到这里,数组里至少有1个空位,必不为满,因此唤醒notFull
            return (T) x;
        } finally {
            System.out.println("remove");
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String args[]) throws InterruptedException {
        int count = 3; // 可以加大数组的size来看更多的过程
        BoundedQueue<Integer> bq = new BoundedQueue<Integer>(count);

        // 开启一个线程执行添加操作
        THREAD_POOL.submit(new Callable<Object>() {
            public Object call() throws InterruptedException {
                for (int i = 0; i < count * 2; i++) {
                    bq.add(i);
                    Thread.sleep(200); // 通过睡眠来制造添加和移除的速度差
                }
                return null;
            }
        });

        // 开启一个线程执行移除操作
        THREAD_POOL.submit(new Callable<Object>() {
            public Object call() throws InterruptedException {
                Thread.sleep(1000);
                for (int i = 0; i < count * 2; i++) {
                    bq.remove();
                    Thread.sleep(50); // 通过睡眠来制造添加和移除的速度差
                }
                return null;
            }
        });
    }  
}

Condition 基础属性

        //条件队列的头节点
        private transient Node firstWaiter;
        //条件队列的尾节点
        private transient Node lastWaiter;

Condition 数据结构

在这里插入图片描述

await 方法

public final void await() throws InterruptedException {
            if (Thread.interrupted())//判断当前线程是否中断,如果被中断则抛出异常
                throw new InterruptedException();
            Node node = addConditionWaiter(); // 添加一个waitStatus为CONDITION的节点到条件队列尾部
            int savedState = fullyRelease(node);//释放锁 这里会考虑重入锁的释放,并且返回重入次数。
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//判断node 是否在同步队列中
                LockSupport.park(this);//阻塞当前线程(当其他线程调用signal()方法时,该线程会从这个位置去执行)
               //要判断当前被阻塞的线程是否是因为interrupt()唤醒
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //重新竞争锁,savedState表示的是被释放的锁的重入次数.
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters(); // 移除waitStatus为CANCELLED的节点
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter 方法

private Node addConditionWaiter() { //将获取锁的线程加入等待队列
            Node t = lastWaiter; //尾节点设置为 t 
            
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters(); // 移除waitStatus不为CONDITION的节点(条件队列里的节点waitStatus都为CONDITION)
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);//创建waitStatus=CONDITION 的节点
            if (t == null) // t为空,代表条件队列为空
                firstWaiter = node;//将头节点设置node
            else
                t.nextWaiter = node;//否则,队列不为空。将t(原尾节点)的后继节点赋值为node
            lastWaiter = node;// 将node赋值给尾节点,即将node放到条件队列的尾部。这里没有用CAS来保证原子性,原因在于调用await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的
            return node;
        }

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; // 如果release失败则将该节点的waitStatus设置为CANCELLED
       }
   }

release 独占模式下释放锁方法

isOnSyncQueue 方法

final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null) // 如果waitStatus为CONDITION 或 node没有前驱节点,则必然不在同步队列,直接返回false
            return false;
        if (node.next != null) // If has successor, it must be on queue 如果有后继节点,必然是在同步队列中,返回true
            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);   // 自旋循环同步队列 判断node是否为同步队列节点,如果是返回true,否则返回false
    }

signal 方法

public final void signal() {
            if (!isHeldExclusively()) //判断释放锁的线程是否获得锁 ,如果未获得锁则抛出异常
                throw new IllegalMonitorStateException();
            Node first = firstWaiter; //头节点设置为first
            if (first != null)//判断头节点是否为空
                doSignal(first); // 唤醒条件队列的头节点
        }

doSignal 方法

private void doSignal(Node first) { // 将条件队列的头节点移到同步队列
    do {
        if ( (firstWaiter = first.nextWaiter) == null)  // 将first节点赋值为first节点的后继节点(相当于移除first节点),如果first节点的后继节点为空,则将lastWaiter赋值为null
            lastWaiter = null; 
        first.nextWaiter = null;    // 断开first节点对first节点后继节点的关联
    } while (!transferForSignal(first) &&   // transferForSignal:将first节点从条件队列移动到同步队列
             (first = firstWaiter) != null);    // 如果transferForSignal失败,并且first节点不为null,则向下遍历条件队列的节点,直到节点成功移动到同步队列 或者 firstWaiter为null
}

transferForSignal 方法

final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //利用CAS node waitStatus 改初始状态 修改失败则移动到同步队列失败
            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);//将node 添加到同步队列
        int ws = p.waitStatus; //获取node 节前驱节点等待状态
// 如果node前驱节点的状态为CANCELLED(ws>0) 或 使用CAS将waitStatus修改成SIGNAL失败,则代表node的前驱节点无法来唤醒node节点,因此直接调用LockSupport.unpark方法唤醒node节点
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

总结

  1. 调用await和signal方法都需要先获得锁,否则会抛异常。
  2. 调用await方法会新建一个waitStatus为CONDITION、线程为当前线程的节点到条件队列尾部,然后当前线程会释放掉锁,并进入阻塞状态,直到该节点被移到同步队列或者被中断。该节点被移动到同步队列,并不代表该节点线程能立马获得锁,还是需要在同步队列中排队并在必要时候(前驱节点为head)调用tryAcquire方法去获取,如果获取成功则代表获得了锁。
  3. 调用signal方法会将条件队列的头节点移动到同步队列。

参考文献

《java 并发编程的艺术》

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值