【Java并发编程实战】—–“J.U.C”:Condition

在看Condition之前,我们先来看下面这个例子:

工厂类,用来存放、取出商品:

public class Depot {
    private int depotSize;     //仓库大小
    private Lock lock;         //独占锁

    public Depot(){
        depotSize = 0;
        lock = new ReentrantLock();
    }

    /**
     * 商品入库
     * @param value
     */
    public void put(int value){
        try {
            lock.lock();
            depotSize += value;
            System.out.println(Thread.currentThread().getName() + " put " + value +" ----> the depotSize: " + depotSize);
        } finally{
            lock.unlock();
        }
    }

    /**
     * 商品出库
     * @param value
     */
    public void get(int value){
        try {
            lock.lock();
            depotSize -= value;
            System.out.println(Thread.currentThread().getName() + " get " + value +" ----> the depotSize: " + depotSize);
        } finally{
            lock.unlock();
        }
    }
}

生产者,生产商品,往仓库里面添加商品:

public class Producer {
    private Depot depot;

    public Producer(Depot depot){
        this.depot = depot;
    }

    public void produce(final int value){
        new Thread(){
            public void run(){
                depot.put(value);
            }
        }.start();
    }
}

消费者,消费商品,从仓库里面取出商品:

public class Customer {
    private Depot depot;

    public Customer(Depot depot){
        this.depot = depot;
    }

    public void consume(final int value){
        new Thread(){
            public void run(){
                depot.get(value);
            }
        }.start();
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        Depot depot = new Depot();

        Producer producer = new Producer(depot);
        Customer customer = new Customer(depot);

        producer.produce(10);
        customer.consume(5);
        producer.produce(20);
        producer.produce(5);
        customer.consume(35);
    }
}

运行结果:

Thread-0 put 10 ----> the depotSize: 10
Thread-1 get 5 ----> the depotSize: 5
Thread-2 put 20 ----> the depotSize: 25
Thread-3 put 5 ----> the depotSize: 30
Thread-4 get 35 ----> the depotSize: -5

程序的运行结果是没有错误的,先put10、然后get5、put20、put5、get35。程序运行结果非常正确,但是在现实生活中,这个实例存在两处错误:

第一:仓库的容量是有限的,我们不可能无限制的往仓库里面添加商品。

第二:仓库的容量是不可能为负数的,但是最后的结果为-5,与现实存在冲突。

针对于上面两处错误,怎么解决?这就轮到Condition大显神通了。

Condition

通过前面几篇博客我们知道Lock提供了比synchronized更加强大、灵活的锁机制,它从某种程度上来说替代了synchronized方式的使用。Condition从字面上面理解就是条件。对于线程而言它为线程提供了一个含义,以便在某种状态(条件Condition)可能为true的另一个线程通知它之前,一直挂起该线程。

对于Condition,JDK API中是这样解释的:

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其newCondition() 方法。下面我们通过Condition来解决上面的问题:这里只改仓库Depot的代码:

public class Depot {
    private int depotSize;     //仓库大小
    private Lock lock;         //独占锁
    private int capaity;       //仓库容量

    private Condition fullCondition;
    private Condition emptyCondition;

    public Depot(){
        this.depotSize = 0;
        this.lock = new ReentrantLock();
        this.capaity = 15;
        this.fullCondition = lock.newCondition();
        this.emptyCondition = lock.newCondition();
    }

    /**
     * 商品入库
     * @param value
     */
    public void put(int value){
        lock.lock();
        try {
            int left = value;
            while(left > 0){
                //库存已满时,“生产者”等待“消费者”消费
                while(depotSize >= capaity){
                    fullCondition.await();
                }
                //获取实际入库数量:预计库存(仓库现有库存 + 生产数量) > 仓库容量   ? 仓库容量 - 仓库现有库存     :    生产数量
                //                  depotSize   left   capaity  capaity - depotSize     left
                int inc = depotSize + left > capaity ? capaity - depotSize : left;
                depotSize += inc;
                left -= inc;
                System.out.println(Thread.currentThread().getName() + "----要入库数量: " + value +";;实际入库数量:" + inc + ";;仓库货物数量:" + depotSize + ";;没有入库数量:" + left);

                //通知消费者可以消费了
                emptyCondition.signal();
            }
        } catch (InterruptedException e) {
        } finally{
            lock.unlock();
        }
    }

    /**
     * 商品出库
     * @param value
     */
    public void get(int value){
        lock.lock();
        try {
            int left = value;
            while(left > 0){
                //仓库已空,“消费者”等待“生产者”生产货物
                while(depotSize <= 0){
                    emptyCondition.await();
                }
                //实际消费      仓库库存数量     < 要消费的数量     ?   仓库库存数量     : 要消费的数量
                int dec = depotSize < left ? depotSize : left;
                depotSize -= dec;
                left -= dec;
                System.out.println(Thread.currentThread().getName() + "----要消费的数量:" + value +";;实际消费的数量: " + dec + ";;仓库现存数量:" + depotSize + ";;有多少件商品没有消费:" + left);

                //通知生产者可以生产了
                fullCondition.signal();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            lock.unlock();
        }
    }
}

test:

public class Test {
    public static void main(String[] args) {
        Depot depot = new Depot();

        Producer producer = new Producer(depot);
        Customer customer = new Customer(depot);

        producer.produce(10);
        customer.consume(5);
        producer.produce(15);
        customer.consume(10);
        customer.consume(15);
        producer.produce(10);
    }
}

运行结果:

Thread-0----要入库数量: 10;;实际入库数量:10;;仓库货物数量:10;;没有入库数量:0
Thread-1----要消费的数量:5;;实际消费的数量: 5;;仓库现存数量:5;;有多少件商品没有消费:0
Thread-4----要消费的数量:15;;实际消费的数量: 5;;仓库现存数量:0;;有多少件商品没有消费:10
Thread-2----要入库数量: 15;;实际入库数量:15;;仓库货物数量:15;;没有入库数量:0
Thread-4----要消费的数量:15;;实际消费的数量: 10;;仓库现存数量:5;;有多少件商品没有消费:0
Thread-5----要入库数量: 10;;实际入库数量:10;;仓库货物数量:15;;没有入库数量:0
Thread-3----要消费的数量:10;;实际消费的数量: 10;;仓库现存数量:5;;有多少件商品没有消费:0

在Condition中,用await()替换wait(),用signal()替换 notify(),用signalAll()替换notifyAll(),对于我们以前使用传统的Object方法,Condition都能够给予实现。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-27 19:39:36

【Java并发编程实战】—–“J.U.C”:Condition的相关文章

【Java并发编程实战】—–“J.U.C”:ReentrantReadWriteLock

ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/读"."读/写"."写/写"操作都不能同时发生.然而在实际的场景中我们就会遇到这种情况:有些资源并发的访问中,它大部分时间都是执行读操作,写操作比较少,但是读操作并不影响数据的一致性,如果在进行读操作时采用独占的锁机制,这样势必会大大降低吞吐量.所以如果能够做

【Java并发编程实战】—–“J.U.C”:CyclicBarrier

在上篇博客([Java并发编程实战]-–"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么介绍的: 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point).在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用.因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier. Cy

【Java并发编程实战】—–“J.U.C”:CountDownlatch

上篇博文([Java并发编程实战]-–"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务".而CountDownlatch和它也有一点点相似之处:CountDownlatch所描述的是"在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待".在JDK API中是这样阐述的: 用给定的计数 初始化 Co

【Java并发编程实战】—–“J.U.C”:Semaphore

信号量Semaphore是一个控制访问多个共享资源的计数器,它本质上是一个"共享锁". Java并发提供了两种加锁模式:共享锁和独占锁.前面LZ介绍的ReentrantLock就是独占锁.对于独占锁而言,它每次只能有一个线程持有,而共享锁则不同,它允许多个线程并行持有锁,并发访问共享资源. 独占锁它所采用的是一种悲观的加锁策略,  对于写而言为了避免冲突独占是必须的,但是对于读就没有必要了,因为它不会影响数据的一致性.如果某个只读线程获取独占锁,则其他读线程都只能等待了,这种情况下就限

【Java并发编程实战】—– AQS(四):CLH同步队列

在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁.入队列.释放锁等实现都与头尾节点相关.而且每一个节点都引入前驱节点和后兴许节点的引用:在等待机制上由原来的自旋改成堵塞唤醒. 其结构例如以下: 知道其结构了,我们再看看他的实现.在线程获取锁时会调用AQS的acquire()方法.该方法第一次尝试获取锁假设

java并发编程实战学习(3)--基础构建模块

转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到空间可用:如果队列为空,那么take方法将阻塞直到有元素可用.队列可以是有界的也可以是无界的. 如果生产者生成工作的速率比消费者处理工作的速率款,那么工作项会在队列中累计起来,最终好紧内存.同样,put方法的阻塞特性也极大地简化了生产者的编码.如果使用有界队列,当队列充满时,生产者将阻

《java并发编程实战》笔记(一)

最近在看<java并发编程实战>,希望自己有毅力把它读完. 线程本身有很多优势,比如可以发挥多处理器的强大能力.建模更加简单.简化异步事件的处理.使用户界面的相应更加灵敏,但是更多的需要程序猿面对的是安全性问题.看下面例子: public class UnsafeSequence { private int value; /*返回一个唯一的数值*/ public int getNext(){ return value++; } } UnsafeSequence的问题在于,如果执行时机不对,那么

《Java并发编程实战》第十六章 Java内存模型 读书笔记

Java内存模型是保障多线程安全的根基,这里仅仅是认识型的理解总结并未深入研究. 一.什么是内存模型,为什么需要它 Java内存模型(Java Memory Model)并发相关的安全发布,同步策略的规范.一致性等都来自于JMM. 1 平台的内存模型 在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证. JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层平台内存模型之间的

《Java并发编程实战》第八章 线程池的使用 读书笔记

一.在任务与执行策略之间的隐性解耦 有些类型的任务需要明确地指定执行策略,包括: . 依赖性任务.依赖关系对执行策略造成约束,需要注意活跃性问题.要求线程池足够大,确保任务都能放入. . 使用线程封闭机制的任务.需要串行执行. . 对响应时间敏感的任务. . 使用ThreadLocal的任务. 1. 线程饥饿死锁 线程池中如果所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞,这种现象称为线程饥饿死锁. 2. 运行时间较长的任务 Java提供了限时版本与无限时版本.例如Thread

《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

造成开销的操作包括: 1. 线程之间的协调(例如:锁.触发信号以及内存同步等) 2. 增加的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 运行速度涉及以下两个指标: 某个指定的任务单元需要"多快"才能处理完成.计算资源一定的情况下,能完成"多少"工作. 可伸缩性: 当增加计算资源时(例如:CPU.内存.存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加. 2 评估各种性能权衡因素 避免不成熟的优化.首先使程序正