关闭

Java Condition 的await(), singal(), singalAll() 具体代码实现分析,

标签: 多线程ConditionnotifyAllwaitnotify
175人阅读 评论(0) 收藏 举报
分类:

前面本博的JavaJava 同步监视器中的 wait() 和 notify() 方法的具体实现 讨论了Java Syncronized关键字中,同步监视器的wait(),notify(),notifyAll()方法的具体实现细节,

而本文将对Java Lock的Condition的await(), singal(), singalAll()具体代码实现做浅尝分析。

为了更好的理解Lock和Condition的使用场景,下面我们先来实现这样一个功能:有多个生产者,多个消费者,一个产品容器,我们假设容器最多可以放3个产品,如果满了,生产者需要等待产品被消费,如果没有产品了,消费者需要等待。我们的目标是一共生产10个产品,最终消费10个产品,如何在多线程环境下完成这一挑战呢?下面是我简单实现的一个demo,仅供参考。

packagecom.lock.condition.test;
 
importjava.util.LinkedList;
importjava.util.concurrent.TimeUnit;
importjava.util.concurrent.atomic.AtomicInteger;
importjava.util.concurrent.locks.Condition;
importjava.util.concurrent.locks.ReentrantLock;
 
publicclass LockConditionTest {
    // 生产 和 消费 的最大总数
    publicstatic int totalCount = 10;
    // 已经生产的产品数
    publicstatic volatile int hasProduceCount = 0;
    // 已经消费的产品数
    publicstatic volatile int hasConsumeCount = 0;
    // 容器最大容量
    publicstatic int containerSize = 3;
    // 使用公平策略的可重入锁,便于观察演示结果
    publicstatic ReentrantLock lock = newReentrantLock(true);
    publicstatic Condition notEmpty = lock.newCondition();
    publicstatic Condition notFull = lock.newCondition();
    // 容器
    publicstatic LinkedList<integer> container = newLinkedList<integer>();
    // 用于标识产品
    publicstatic AtomicInteger idGenerator = newAtomicInteger();
 
    publicstatic void main(String[] args) {
        Thread p1 = newThread(newProducer(), "p-1");
        Thread p2 = newThread(newProducer(), "p-2");
        Thread p3 = newThread(newProducer(), "p-3");
 
        Thread c1 = newThread(newConsumer(), "c-1");
        Thread c2 = newThread(newConsumer(), "c-2");
        Thread c3 = newThread(newConsumer(), "c-3");
 
        c1.start();
        c2.start();
        c3.start();
        p1.start();
        p2.start();
        p3.start();
        try{
            c1.join();
            c2.join();
            c3.join();
            p1.join();
            p2.join();
            p3.join();
        }catch(Exception e){
 
        }
        System.out.println(" done. ");
    }
    staticclass Producer implementsRunnable{
        @Override
        publicvoid run() {
            while(true){
                lock.lock();
                try{
                    // 容器满了,需要等待非满条件
                    while(container.size() >= containerSize){
                        notFull.await();
                    }
 
                    // 到这里表明容器未满,但需要再次判断是否已经完成了任务
                    if(hasProduceCount >= totalCount){
                        System.out.println(Thread.currentThread().getName()+" producer exit");
                        return;
                    }
 
                    intproduct = idGenerator.incrementAndGet();
                    // 把生产出来的产品放入容器
                    container.addLast(product);
                    System.out.println(Thread.currentThread().getName() + " product " + product);
                    hasProduceCount++;
 
                    // 通知消费线程可以去消费了
                    notEmpty.signal();
                }catch(InterruptedException e) {
                }finally{
                    lock.unlock();
                }
            }
        }
    }
    staticclass Consumer implementsRunnable{
        @Override
        publicvoid run() {
            while(true){
                lock.lock();
                try{
                    if(hasConsumeCount >= totalCount){
                        System.out.println(Thread.currentThread().getName()+" consumer exit");
                        return;
                    }
 
                    // 一直等待有产品了,再继续往下消费
                    while(container.isEmpty()){
                        notEmpty.await(2, TimeUnit.SECONDS);
                        if(hasConsumeCount >= totalCount){
                            System.out.println(Thread.currentThread().getName()+" consumer exit");
                            return;
                        }
                    }
 
                    Integer product = container.removeFirst();
                    System.out.println(Thread.currentThread().getName() + " consume " + product);
                    hasConsumeCount++;
 
                    // 通知生产线程可以继续生产产品了
                    notFull.signal();
                }catch(InterruptedException e) {
                }finally{
                    lock.unlock();
                }
            }
        }
    }
}
一次执行的结果如下:
p-1product 1
p-3product 2
p-2product 3
c-3consume 1
c-2consume 2
c-1consume 3
p-1product 4
p-3product 5
p-2product 6
c-3consume 4
c-2consume 5
c-1consume 6
p-1product 7
p-3product 8
p-2product 9
c-3consume 7
c-2consume 8
c-1consume 9
p-1product 10
p-3producer exit
p-2producer exit
c-3consume 10
c-2consumer exit
c-1consumer exit
p-1producer exit
c-3consumer exit
 done.

上面的示例只是为了展示 Lock结合Condition可以实现的一种经典场景,在有了感性的认识之后,我们将一步一步来观察Lock和Condition是如何协作完成这一任务的,这也是本篇的核心内容。

为了更好的理解和演示这一个过程,我们使用到的锁是使用公平策略模式的,我们会使用上面例子运作的流程。我们会使用到3个生产线程,3个消费线程,分别表示 p1、p2、p3和c1、c2、c3。

Condition的内部实现是使用节点链来实现的,每个条件实例对应一个节点链,我们有notEmpty 和 notFull 两个条件实例,所以会有两个等待节点链。

一切准备就绪 ,开始我们的探索之旅。

1、线程c3执行,然后发现没有产品可以消费,执行 notEmpty.await,进入等待队列中等候。

这里写图片描述

2、线程c2和线程c1执行,然后发现没有产品可以消费,执行 notEmpty.await,进入等待队列中等候。

这里写图片描述

3、 线程 p1 启动,得到了锁,p1开始生产产品,这时候p3抢在p2之前,执行了lock操作,结果p2和p3都处于等待状态,入同步队列等待。

注意,本例中我们使用的是公平策略模式下的排它锁,由于p3抢先执行取锁操作,所以虽然p2和p3都被阻塞了,但是p3会优先被唤醒 。

4、这会,p1生产完毕,通知 not empty等待队列,可以唤醒一个等待线程节点了,然后释放了锁,释放锁会导致p3被唤醒,然后p1进入下一个循环,进入同步队列。

这里写图片描述

事情开始变得有趣了,p1执行一次生产后,执行了 notEmpty.signal,其效果就是把 not empty等待列表中的头节点,即c3节点移到同步等待列队中,重新参与抢占锁。

5、p3生产完了产品后,继续notEmpty.signal,同时释放锁,释放锁后会唤醒p2线程,然后p3在下一轮尝试获取锁的时候,再次入队。

这里写图片描述

6、接着,p2继续生产,生产后执行 notEmpty.signal,同时释放锁,释放锁后唤醒c3线程,然后p2在下一轮尝试取锁的时候,入列。

这里写图片描述

7、c3进行消费,你可以看到,现在 not empty等待列队中已经没有等待节点了,由于我们使用的是公平策略排它锁,这就会导致同步队列中的节点一个接着一个执行,而目前同步队列中的节点排列为一生产,一消费,这不难可以知道,接下来代码已经不会进入 wait条件了,所以一个一个轮流执行就是,比如c3,执行完了,继续notFull.signal(); 然后释放锁,入队,这里要明白,notFull.signal();这句代码其实没有作用了,因为 not full等待队列中没有任何等待线程节点。 c3执行后,状态如下图所示:

这里写图片描述

8、后面的事情我想大家都可以想得出来是怎样一步一步交替执行的了。

总结

本篇基于一个实例来演示结合Lock和Condition如何实现生产-消费模式,而且只讨论一种可能执行的流程,是想更简单的表述AQS底层是如何实现的。基于上面这个演示过程,针对其它的执行流程,其原来也是一样的。

Condition内部使用一个节点链来保存所有 wait状态的线程,当对应条件被signal的时候,就会把等待节点转移到同步队列中(AQS 的CLH 队列中),继续竞争锁。


0
0
查看评论

java Condition源码分析

JUC提供了Lock可以方便的进行锁操作,但是有时候我们也需要对线程进行条件性的阻塞和唤醒,这时我们就需要condition条件变量,它就像是在线程上加了多个开关,可以方便的对持有锁的线程进行阻塞和唤醒。Condition的概念Condition主要是为了在J.U.C框架中提供和Java传统的监视器...
  • qilixiang012
  • qilixiang012
  • 2015-04-23 09:21
  • 1673

异步操作校验工具awaitility源码分析

1. 背景之前介绍了一篇awaitility快速入门的文章:异步校验工具awaitility快速入门,该工具很好地解决了校验异步操作的问题,其中封装了很多便捷的用法,重点是在规定时间内,轮询结果;本文以源码的方式,介绍一下工具内部是怎么实现的,理解开发的设计思路,对以后解决工作中的问题是有帮助的。2...
  • neven7
  • neven7
  • 2017-02-21 21:13
  • 480

《Java源码分析》:Semaphore

《Java源码分析》:SemaphoreSemaphore 是一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semap...
  • u010412719
  • u010412719
  • 2016-08-03 15:55
  • 995

condition对象,这个对象的await()和singal()方法

使用stop()不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时其他任何线程都不能访问...
  • meishibuyaodarao
  • meishibuyaodarao
  • 2015-08-25 23:49
  • 493

Lock的lock/unlock, condition的await/singal 和 Object的wait/notify 的区别 - 永远是 ...

在使用Lock之前,我们都使用Object 的wait和notify实现同步的。举例来说,一个producer和consumer,consumer发现没有东西了,等待,produer生成东西了,唤醒。 线程consumer 线程producer synchronize(obj){ ...
  • fei33423
  • fei33423
  • 2017-04-13 23:19
  • 589

Java多线程(3):使用Condition中的await、signal进行线程间协作

详情见《Java语言程序设计-进阶篇》P238 使用场景: 使用condition可以实现线程协作。取款线程发现账户余额不足,先停止自己的线程,等待存款线程存入钱后再叫醒该取款线程重新取钱。编码流程:从ReentrantLock对象获得Condition对象Lock lock=new Reentra...
  • csd54496
  • csd54496
  • 2016-10-09 18:47
  • 567

jdk1.8 J.U.C并发源码阅读------AQS之conditionObject内部类分析

jdk1.8中java.util.concurrent包中源码阅读笔记。
  • u011470552
  • u011470552
  • 2017-08-02 10:10
  • 426

Condition的await-signal流程详解

大概的整个过程是: 调用await的线程都会进入一个Condition队列。调用signal的线程每一次都会从firstWaiter开始找出未取消的Condition Node放到release队列里,然后调用signal的线程在await或者unlock的时候执行release方法才...
  • luonanqin
  • luonanqin
  • 2014-12-12 15:53
  • 11158

wait()、notify()和notifyAll()、sleep()、Condition、await()、signal()

wait()、notify()和notifyAll()是 Object类 中的方法 从这三个方法的文字描述可以知道以下几点信息: 1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。 2)调用某个对象的wait()方法能让当前线程阻塞,并...
  • shinecjj
  • shinecjj
  • 2016-08-07 22:11
  • 1376

Condition源码解析

在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait()、notify()系列方法可以实现等待/通知模式。在Java SE5后,Java提供了Lock接口,相对于Synchronized而言,Lock提供了条件Condition,对线程的等待、唤醒操作...
  • jeffleo
  • jeffleo
  • 2017-08-11 16:45
  • 265
    个人资料
    • 访问:736347次
    • 积分:11559
    • 等级:
    • 排名:第1582名
    • 原创:388篇
    • 转载:610篇
    • 译文:1篇
    • 评论:35条
    最新评论