java并发等待条件的实现原理(Condition)
来源:互联网 发布:校音器软件下载 编辑:程序博客网 时间:2024/05/22 05:30
前言
前面介绍了排它锁,共享锁的实现机制,本篇继续学习AQS中的另外一个内容-Condition。想必学过java的都知道Object.wait和Object.notify,同时也应该知晓这两个方法的使用离不开synchronized关键字。synchronized是jvm级别提供的同步原语,它的实现机制隐藏在jvm实现中。作为Lock系列功能中的Condition,就是用来实现类似 Object.wait和Object.notify 对应功能的。
使用场景
为了更好的理解Lock和Condition的使用场景,下面我们先来实现这样一个功能:有多个生产者,多个消费者,一个产品容器,我们假设容器最多可以放3个产品,如果满了,生产者需要等待产品被消费,如果没有产品了,消费者需要等待。我们的目标是一共生产10个产品,最终消费10个产品,如何在多线程环境下完成这一挑战呢?下面是我简单实现的一个demo,仅供参考。
package com.lock.condition.test;import java.util.LinkedList;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class LockConditionTest { // 生产 和 消费 的最大总数 public static int totalCount = 10; // 已经生产的产品数 public static volatile int hasProduceCount = 0; // 已经消费的产品数 public static volatile int hasConsumeCount = 0; // 容器最大容量 public static int containerSize = 3; // 使用公平策略的可重入锁,便于观察演示结果 public static ReentrantLock lock = new ReentrantLock(true); public static Condition notEmpty = lock.newCondition(); public static Condition notFull = lock.newCondition(); // 容器 public static LinkedList<Integer> container = new LinkedList<Integer>(); // 用于标识产品 public static AtomicInteger idGenerator = new AtomicInteger(); public static void main(String[] args) { Thread p1 = new Thread(new Producer(), "p-1"); Thread p2 = new Thread(new Producer(), "p-2"); Thread p3 = new Thread(new Producer(), "p-3"); Thread c1 = new Thread(new Consumer(), "c-1"); Thread c2 = new Thread(new Consumer(), "c-2"); Thread c3 = new Thread(new Consumer(), "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. "); } static class Producer implements Runnable{ @Override public void run() { while(true){ lock.lock(); try{ // 容器满了,需要等待非满条件 while(container.size() >= containerSize){ notFull.await(); } // 到这里表明容器未满,但需要再次判断是否已经完成了任务 if(hasProduceCount >= totalCount){ System.out.println(Thread.currentThread().getName()+" producer exit"); return ; } int product = idGenerator.incrementAndGet(); // 把生产出来的产品放入容器 container.addLast(product); System.out.println(Thread.currentThread().getName() + " product " + product); hasProduceCount++; // 通知消费线程可以去消费了 notEmpty.signal(); } catch (InterruptedException e) { }finally{ lock.unlock(); } } } } static class Consumer implements Runnable{ @Override public void 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-1 product 1p-3 product 2p-2 product 3c-3 consume 1c-2 consume 2c-1 consume 3p-1 product 4p-3 product 5p-2 product 6c-3 consume 4c-2 consume 5c-1 consume 6p-1 product 7p-3 product 8p-2 product 9c-3 consume 7c-2 consume 8c-1 consume 9p-1 product 10p-3 producer exitp-2 producer exitc-3 consume 10c-2 consumer exitc-1 consumer exitp-1 producer exitc-3 consumer exit done.
从结果可以发现已经达到我们的目的了。
深入理解Condition的实现原理
上面的示例只是为了展示 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的时候,就会把等待节点转移到同步队列中,继续竞争锁。原理其实并不复杂,有兴趣的朋友可以翻阅源码。
- java并发等待条件的实现原理(Condition)
- 【java并发】条件阻塞Condition的应用
- java并发包:重入锁与Condition条件
- Java多线程Condition实现等待/通知
- java并发编程 之 Condition(等待和通知)
- Python 线程条件(Condition),(线程等待另一个线程的执行)
- Java并发之读写锁Lock和条件阻塞Condition的应用
- Java并发之读写锁Lock和条件阻塞Condition的应用
- Java并发之读写锁Lock和条件阻塞Condition的应用
- 【Java多线程与并发库】11.java5条件阻塞Condition的应用
- Java Thread&Concurrency(14): 深入理解条件队列(Condition)及其实现原理
- java5条件阻塞Condition的应用-多路等待通知Lock-Condition使用-笔记整理10
- java并发包学习系列:重入锁与Condition条件
- 【Java高并发学习】重入锁ReentrantLock、Condition条件及信号量
- java并发锁ReentrantLock源码分析二之Condition实现原理
- java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)
- 并发学习之:对条件变量(condition variable)的讨论
- 并发学习之:对条件变量(condition variable)的讨论
- ios 下全局断点或者局部断点xcode会闪退
- 学习Hypermesh
- 课堂练习
- 二叉树的基本用法总结
- 算法学习笔记之图解冒泡法
- java并发等待条件的实现原理(Condition)
- Web响应式设计(一)
- oracle归档日志清理
- WPF自定义控件
- 包干到户是最好的软件项目管理方法
- ping: unknown host www.baidu.com解决
- JAVA中通过JNI调用C程序实例教程
- 7.13 BHDU 3671 Boonie and Clyde
- 指针铁律1:指针也是一种数据类型