关于java:JUCCondition

57次阅读

共计 4698 个字符,预计需要花费 12 分钟才能阅读完成。

JUC-Condition

在应用 Lock 之前,咱们应用的最多的同步形式应该是 synchronized 关键字来实现同步形式了。配合 Object 的 wait()、notify()系列办法能够实现期待 / 告诉模式。Condition 接口也提供了相似 Object 的监视器办法,与 Lock 配合能够实现期待 / 告诉模式,然而这两者在应用形式以及性能个性上还是有差异的。Object 和 Condition 接口的一些比照。摘自《Java 并发编程的艺术》

一、Condition 接口介绍和示例

​ 首先咱们须要明确 condition 对象是依赖于 lock 对象的,意思就是说 condition 对象须要通过 lock 对象进行创立进去 (调用 Lock 对象的 newCondition() 办法)。consition 的应用形式十分的简略。然而须要留神在调用办法前获取锁。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ConditionDemo
 */
public class ConditionDemo {private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public static void main(String[] args) {ConditionDemo demo = new ConditionDemo();
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        // AQS, condition.signal()的信号会给队列首位的线程,executorService.execute(demo::conditionWait);
        executorService.execute(demo::conditionWait);
        executorService.execute(demo::conditionSignal);
        executorService.execute(demo::conditionSignal);
    }

    private void conditionWait() {lock.lock();
        try {System.out.println(Thread.currentThread().getName() + "拿到锁了");
            System.out.println(Thread.currentThread().getName() + "期待信号");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "拿到信号");
        } catch (Exception e) {e.printStackTrace();
        } finally {lock.unlock();
        }
    }

    private void conditionSignal() {lock.lock();
        try {Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + "拿到锁了");
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "发出信号");
        } catch (Exception e) {e.printStackTrace();
        } finally {lock.unlock();
        }
    }
}
pool-1-thread- 1 拿到锁了
pool-1-thread- 1 期待信号
pool-1-thread- 2 拿到锁了
pool-1-thread- 2 期待信号
pool-1-thread- 3 拿到锁了
pool-1-thread- 3 发出信号
pool-1-thread- 4 拿到锁了
pool-1-thread- 4 发出信号
pool-1-thread- 1 拿到信号
pool-1-thread- 2 拿到信号

如示例所示,个别都会将 Condition 对象作为成员变量。当调用 await()办法后,以后线程会开释锁并在此期待,而其余线程调用 Condition 对象的 signal()办法,告诉以后线程后,以后线程才从 await()办法返回,并且在返回前曾经获取了锁。

二、Condition 接口罕用办法

​ condition 能够艰深的了解为条件队列。当一个线程在调用了 await 办法当前,直到线程期待的某个条件为真的时候才会被唤醒。这种形式为线程提供了更加简略的期待 / 告诉模式。Condition 必须要配合锁一起应用,因为对共享状态变量的拜访产生在多线程环境下。一个 Condition 的实例必须与一个 Lock 绑定,因而 Condition 个别都是作为 Lock 的外部实现。

  1. await():造成以后线程在接到信号或被中断之前始终处于期待状态。
  2. await(long time, TimeUnit unit):造成以后线程在接到信号、被中断或达到指定等待时间之前始终处于期待状态。
  3. awaitNanos(long nanosTimeout):造成以后线程在接到信号、被中断或达到指定等待时间之前始终处于期待状态。返回值示意剩余时间,如果在 nanosTimesout 之前唤醒,那么返回值 = nanosTimeout – 耗费工夫,如果返回值 <= 0 , 则能够认定它曾经超时了。
  4. awaitUninterruptibly():造成以后线程在接到信号之前始终处于期待状态。【留神:该办法对中断不敏感】。
  5. awaitUntil(Date deadline):造成以后线程在接到信号、被中断或达到指定最初期限之前始终处于期待状态。如果没有到指定工夫就被告诉,则返回 true,否则示意到了指定工夫,返回返回 false。
  6. signal():唤醒一个期待线程。该线程从期待办法返回前必须取得与 Condition 相干的锁。
  7. signalAll():唤醒所有期待线程。可能从期待办法返回的线程必须取得与 Condition 相干的锁。

三、Condition 接口原理简略解析

​ Condition 是 AQS 的外部类。每个 Condition 对象都蕴含一个队列 (期待队列)。期待队列是一个 FIFO 的队列,在队列中的每个节点都蕴含了一个线程援用,该线程就是在 Condition 对象上期待的线程,如果一个线程调用了 Condition.await() 办法,那么该线程将会开释锁、结构成节点退出期待队列并进入期待状态。期待队列的根本构造如下所示。

期待分为首节点和尾节点。当一个线程调用 Condition.await()办法,将会以以后线程结构节点,并将节点从尾部退出期待队列。新增节点就是将尾部节点指向新增的节点。节点援用更新原本就是在获取锁当前的操作,所以不须要 CAS 保障。同时也是线程平安的操作。

3.1、期待

当线程调用了 await 办法当前。线程就作为队列中的一个节点被退出到期待队列中去了。同时会开释锁的领有。当从 await 办法返回的时候。肯定会获取 condition 相关联的锁。当期待队列中的节点被唤醒的时候,则唤醒节点的线程开始尝试获取同步状态。如果不是通过 其余线程调用 Condition.signal()办法唤醒,而是对期待线程进行中断,则会抛出 InterruptedException 异样信息。

3.2、告诉

调用 Condition 的 signal()办法,将会唤醒在期待队列中期待最长工夫的节点(条件队列里的首节点),在唤醒节点前,会将节点移到同步队列中。以后线程退出到期待队列中如图所示:

在调用 signal()办法之前必须先判断是否获取到了锁。接着获取期待队列的首节点,将其挪动到同步队列并且利用 LockSupport 唤醒节点中的线程。节点从期待队列挪动到同步队列如下图所示:

被唤醒的线程将从 await 办法中的 while 循环中退出。随后退出到同步状态的竞争当中去。胜利获取到竞争的线程则会返回到 await 办法之前的状态。

四、总结

调用 await 办法后,将以后线程退出 Condition 期待队列中。以后线程开释锁。否则别的线程就无奈拿到锁而产生死锁。自旋 (while) 挂起,一直检测节点是否在同步队列中了,如果是则尝试获取锁,否则挂起。当线程被 signal 办法唤醒,被唤醒的线程将从 await()办法中的 while 循环中退出来,而后调用 acquireQueued()办法竞争同步状态。

五、利用 Condition 实现生产者消费者模式

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

/**
 * 利用 Condition 实现生产者消费者模式
 */
public class BoundedQueue {
    // 生产者容器
    private LinkedList<Object> buffer;
    // 容器最大值是多少
    private int maxSize;
    private Lock lock;
    private Condition fullCondition;
    private Condition notFullCondition;

    BoundedQueue(int maxSize) {
        this.maxSize = maxSize;
        buffer = new LinkedList<>();
        lock = new ReentrantLock();
        fullCondition = lock.newCondition();
        notFullCondition = lock.newCondition();}

    /**
     * 生产者
     *
     * @param obj
     * @throws InterruptedException
     */
    public void put(Object obj) throws InterruptedException {
        // 获取锁
        lock.lock();
        try {while (maxSize >= buffer.size()) {
                // 满了,增加的线程进入期待状态
                notFullCondition.await();}
            buffer.add(obj);
            // 告诉
            fullCondition.signal();} finally {lock.unlock();
        }
    }

    /**
     * 消费者
     *
     * @return
     * @throws InterruptedException
     */
    public Object get() throws InterruptedException {
        Object obj;
        lock.lock();
        try {while (buffer.size() == 0) {
                // 队列中没有数据了 线程进入期待状态
                fullCondition.await();}
            obj = buffer.poll();
            // 告诉
            notFullCondition.signal();} finally {lock.unlock();
        }
        return obj;
    }
}

参考:

Java 并发编程的艺术

Java 并发编程网

正文完
 0