这篇里面有一些主要的概念,理解概念是件有意义的事情,仅仅有理解概念才干在面对详细问题的时候找到正确的解决思路。先看一下管程的概念

第一次在书上看到管程这个中文名称认为非常迷糊,管程究竟是个什么东东,于是去找了英文原本对比一看。英文是Monitor,这不是监视器吗,更加迷糊了。为啥翻译成管程?去百科上搜了下管程,管程的定义例如以下:“一个管程定义了一个数据结构和可以并发进程所运行的一组操作,这组操作能同步进程和改变管程中的数据”。

从这个定义中可以看到管程事实上和类的概念非常相似。类是广义的封装了数据和方法,而管程不仅包括了数据和方法,而且它的方法可以同步并发进程的操作。

1. 数据

2. 方法

3. 它的方法可以同步并发进程的操作

说白了,管程就是一个专门为并发编程提出的概念,它表示一个对象自己维护自己的状态,而且可以依据自身状态来同步并发的线程操作。而不是把这样的同步的手段交给调用者来处理。举个样例来说。有一个有界队列。它提供了put和take方法,因为是有界。那么问题来了:

1. 队列满时,不能增加队列,线程得等待

2. 队列空时。不能从队列取元素,线程得等待

假设让调用者自己来控制这样的状态,那么代码可能例如以下。通过不断轮询状态,直到退出轮询

                while(true){
if(array.isFull()){
Thread.sleep(100);
}
}

这样的方式是很低效而且存在问题的,由于在并发情况下。假设不加锁的话。状态是难以控制的。

所以一种更好的方法是使用管程这样的结构,由并发对象自己控制自己的状态并来同步线程操作。

接下来看下条件谓词的概念。谓词就是动词,表示一种动作。条件谓词指的是检查管程状态的动作,比方

1. isFull 是否满

2. isEmpty 是否空

条件谓词是状态改变操作的前提条件。须要不断的轮询条件谓词直到满足才干进行状态改变操作。

再看条件队列这个概率,条件队列指的是一组在等待某个条件变成真的线程,队列中的元素是线程

一个条件队列肯定和一个锁相关联。比較每一个Java对象都有一个内置锁,用synchronized操作能够获得内置锁,相同,每一个Java对象都有一个条件队列。当须要获得内置锁时。并发的线程就进入了条件队列, Object的wait(), notify(), notifyAll()操作能够操作条件队列。

1. wait()方法将会让当前线程进入条件队列等待,而且释放锁。

这点和Thread.sleep不一样,Thread.sleep会让线程睡眠。可是不释放锁。

须要注意的是wait()方法的退出条件是它被notify或者notifyAll方法唤醒了,而且在重新的锁竞争中获得了锁,也就说,当wait方法退出时。当前线程还是是持有锁的。

2. notify()方法,从条件队列的线程中随即唤醒一个线程,并让它去參与锁竞争

3. notifyAll()方法,唤醒条件队列中全部的等待线程,让它们參与锁竞争

Java 1.5之后新增了显式锁的接口java.util.concurrent.locks.Lock接口。相同提供了显式的条件接口Condition,并对条件队列进行了增强。

一个内置锁仅仅能相应一个条件队列。这有个缺陷。就是当一个锁相应多个条件谓词时,多个条件谓词仅仅能公用一个条件队列,这时候唤醒等待线程时有可能出现唤醒丢失的情况。

比方上面有界队列的情况,有两个条件谓词 isFull 和 isEmpty,当对两个条件谓词都进行wait()时,假设使用notify()方法来唤醒的话,仅仅是会从条件队列中选取一个线程,并不知道这个线程是在哪个条件谓词上等待,这就出现了所谓的唤醒丢失的情况。所以使用内置条件队列时。最好使用notifyAll()方法来唤醒全部的线程,避免出现唤醒丢失这个活跃性问题。可是notifyAll是一个重的方法,它会带来大量的上下文切换和锁竞争。

显式锁和显式条件队列避免了这个问题,一个显示锁能够相应多个条件Condition,一个Condition维护一个条件队列,这样对于多个条件谓词,比方isFull和isEmpty,能够使用两个Condition。对每一个条件谓词单独await,唤醒时能够单独signal。效率更高。

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
// 创建一个条件
Condition newCondition();
} // Condition接口封装了条件队列的方法
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}

后面会有详细的样例来比較使用内次锁和内置条件队列以及使用显式锁和显式条件队列的差别。

可以看到管程中可以依据状态同步线程操作(主要是让线程等待)的方法的写法有固定的流程,是个三元组: 锁,条件谓词。wait()方法

1. 先获得锁

2. 轮询条件谓词直到满足条件

3. 一个wait要相应一个notify或notifyAll。值得注意的是,使用wait()方法必需要先获取锁

对于内置锁,写法例如以下

                public synchronized void put(T item) throws InterruptedException {
while(isFull()){
wait();
}
......
}

相应显式锁,写法例如以下

                public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == array.length) {
isFull.await();
}
}finally{
lock.unlock();
}
}

以下我们使内置锁和内置条件队列实现一个堵塞队列:

1. 有两个条件谓词 isFull()和 isEmpty()来推断队列是否满和是否空

2. 当put方法时,先获取内置锁,然后轮询isFull()状态。假设满就使用内置条件队列的wait()方法让线程等待。

当不满时,wait()方法会被notify唤醒,然后竞争锁,直到获得锁。进入以下的流程

改动完状态后,须要调用notifyAll()方法做一次唤醒操作,须要注意的时。put方法里面的notifyAll是为了唤醒在isEmpty条件谓词等待的线程。

可是因为一个内置锁仅仅能有一个条件队列。所以notifyAll也会唤醒在isFull条件谓词等待的线程,这样会带来性能的消耗。

假设这里使用notify()方法。就会发生唤醒丢失。由于notify()方法仅仅负责唤醒条件队列的一个线程,不知道它在哪个条件谓词等待。

假设唤醒的是在isFull条件谓词等待的线程时,就发生了唤醒丢失。

3. take方法同put方法一样,仅仅是take在isEmpty条件谓词等待。改动完状态后。相同须要notifyAll全部的线程来竞争锁。

package com.zc.lock;

public class BlockingArray<T> {
private final T[] array; private int head; private int tail; private int count; public BlockingArray(int size){
array = (T[])new Object[size];
} public synchronized void put(T item) throws InterruptedException {
while(isFull()){
wait();
} array[tail] = item;
if(++ tail == array.length){
tail = 0;
}
count ++;
System.out.println("Add item: " + item);
// 通知条件队列有元素进入
notifyAll();
} public synchronized T take() throws InterruptedException {
while(isEmpty()){
wait();
} T item = array[head];
if(++ head == array.length){
head = 0;
}
count --;
System.out.println("Take item: " + item);
// 通知条件队列有元素出去
notifyAll();
return item;
} public synchronized boolean isFull(){
return count == array.length;
} public synchronized boolean isEmpty(){
return count == 0;
}
}

以下有显式锁Lock和显式条件Condition来实现一个堵塞队列

1. 定义了一个ReentrantLock显式锁

2. 由这个显式锁创建两个条件相应isFull条件谓词和isEmpty条件谓词,这两个条件都是绑定的同一个Lock对象

3. put方法时。先获得显式锁,然后轮询队列是否满,假设满了就用Condition的await()来让线程等待。当队列不满时,await()方法被signal()方法唤醒。竞争锁直到退出await()方法。改动完状态会,单独对isEmpty的条件谓词唤醒,使用isEmpty条件的signal方法单独对在isEmpty等待的线程唤醒,这样效率比notifyAll高非常多

4. take方法和put原理一样

package com.zc.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class BlockingArrayWithCondition<T> {
private final T[] array; private int head; private int tail; private int count; private java.util.concurrent.locks.Lock lock = new ReentrantLock(); private Condition isFull = lock.newCondition(); private Condition isEmpty = lock.newCondition(); public BlockingArrayWithCondition(int size) {
array = (T[]) new Object[size];
} public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == array.length) {
isFull.await();
} array[tail] = item;
if (++tail == array.length) {
tail = 0;
}
count++;
System.out.println("Add item: " + item);
// 通知isEmpty条件队列有元素进入
isEmpty.signal();
} finally {
lock.unlock();
}
} public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
isEmpty.await();
} T item = array[head];
if (++head == array.length) {
head = 0;
}
count--;
System.out.println("Take item: " + item);
// 通知isFull条件队列有元素出去
isFull.signal();
return item;
} finally {
lock.unlock();
}
}
}

以下我们写一个測试用例对这个堵塞队列进行測试

1. 使用100个线程往堵塞队列里面put() 1到100的数字

2. 使用100个线程从堵塞队列take一个数

3. 最后的结果应该是放入了1到100个数字,取出了1到100个数字,不会有反复数字,也不会有数字丢失

4. 一个数肯定是先put后take

package com.zc.lock;

import java.util.concurrent.atomic.AtomicInteger;

public class BlockingArrayTest {
public static void main(String[] args){
//final BlockingArray<Integer> blockingArray = new BlockingArray<Integer>(10); final BlockingArrayWithCondition<Integer> blockingArray = new BlockingArrayWithCondition<Integer>(10); final AtomicInteger count = new AtomicInteger(0); for(int i = 0; i < 100; i ++){
Thread t = new Thread(new Runnable(){ @Override
public void run() {
try {
blockingArray.put(count.incrementAndGet());
} catch (InterruptedException e) {
e.printStackTrace();
}
} });
t.start();
} for(int i = 0; i < 100; i ++){
Thread t = new Thread(new Runnable(){ @Override
public void run() {
try {
blockingArray.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
} });
t.start();
} }
}

測试结果例如以下,证明堵塞队列的实现是正确的:

1. 放入了100个数。取出了100个数,没有反复的数字。也有没有数字丢失

2. 数字先放入后取出

Add item: 1
Add item: 2
Add item: 3
Add item: 4
Add item: 5
Add item: 6
Add item: 7
Add item: 8
Add item: 9
Add item: 10
Take item: 1
Take item: 2
Add item: 11
Add item: 12
Take item: 3
Take item: 4
Add item: 13
Take item: 5
Take item: 6
Take item: 7
Take item: 8
Take item: 9
Add item: 14
Take item: 10
Take item: 11
Take item: 12
Add item: 15
Take item: 13
Take item: 14
Take item: 15
Add item: 16
Add item: 17
Add item: 18
Take item: 16
Take item: 17
Take item: 18
Add item: 19
Take item: 19
Add item: 20
Take item: 20
Add item: 21
Take item: 21
Add item: 22
Take item: 22
Add item: 23
Add item: 24
Take item: 23
Take item: 24
Add item: 25
Take item: 25
Add item: 26
Take item: 26
Add item: 27
Take item: 27
Add item: 28
Take item: 28
Add item: 29
Take item: 29
Add item: 30
Take item: 30
Add item: 31
Take item: 31
Add item: 32
Take item: 32
Add item: 33
Take item: 33
Add item: 34
Take item: 34
Add item: 35
Take item: 35
Add item: 36
Take item: 36
Add item: 37
Take item: 37
Add item: 38
Take item: 38
Add item: 39
Take item: 39
Add item: 40
Take item: 40
Add item: 41
Take item: 41
Add item: 42
Take item: 42
Add item: 43
Take item: 43
Add item: 44
Take item: 44
Add item: 45
Take item: 45
Add item: 46
Take item: 46
Add item: 47
Take item: 47
Add item: 48
Take item: 48
Add item: 49
Take item: 49
Add item: 50
Take item: 50
Add item: 51
Take item: 51
Add item: 52
Take item: 52
Add item: 53
Take item: 53
Add item: 54
Take item: 54
Add item: 55
Take item: 55
Add item: 56
Take item: 56
Add item: 57
Take item: 57
Add item: 58
Take item: 58
Add item: 59
Take item: 59
Add item: 60
Take item: 60
Add item: 61
Take item: 61
Add item: 62
Take item: 62
Add item: 63
Take item: 63
Add item: 64
Take item: 64
Add item: 65
Take item: 65
Add item: 66
Take item: 66
Add item: 67
Take item: 67
Add item: 68
Take item: 68
Add item: 69
Take item: 69
Add item: 70
Take item: 70
Add item: 71
Take item: 71
Add item: 72
Take item: 72
Add item: 73
Take item: 73
Add item: 74
Take item: 74
Add item: 75
Take item: 75
Add item: 76
Take item: 76
Add item: 77
Take item: 77
Add item: 78
Take item: 78
Add item: 79
Take item: 79
Add item: 80
Take item: 80
Add item: 81
Take item: 81
Add item: 82
Take item: 82
Add item: 83
Take item: 83
Add item: 84
Take item: 84
Add item: 85
Take item: 85
Add item: 86
Take item: 86
Add item: 87
Take item: 87
Add item: 88
Take item: 88
Add item: 89
Take item: 89
Add item: 90
Take item: 90
Add item: 91
Take item: 91
Add item: 92
Take item: 92
Add item: 93
Take item: 93
Add item: 94
Take item: 94
Add item: 95
Take item: 95
Add item: 96
Take item: 96
Add item: 97
Take item: 97
Add item: 98
Take item: 98
Add item: 99
Take item: 99
Add item: 100
Take item: 100

转载请注明来源: http://blog.csdn.net/iter_zc

聊聊高并发(十四)理解Java中的管程,条件队列,Condition以及实现一个堵塞队列的更多相关文章

  1. 十分钟理解Java中的动态代理

    十分钟理解 Java 中的动态代理   一.概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道 ...

  2. 聊聊高并发(四十)解析java.util.concurrent各个组件(十六) ThreadPoolExecutor源代码分析

    ThreadPoolExecutor是Executor运行框架最重要的一个实现类.提供了线程池管理和任务管理是两个最主要的能力.这篇通过分析ThreadPoolExecutor的源代码来看看怎样设计和 ...

  3. 【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  4. 聊聊高并发(四十四)解析java.util.concurrent各个组件(二十) Executors工厂类

    Executor框架为了更方便使用,提供了Executors这个工厂类.通过一系列的静态工厂方法.能够高速地创建对应的Executor实例. 仅仅有一个nThreads參数的newFixedThrea ...

  5. 【Java学习笔记之十四】Java中this用法小节

    用类名定义一个变量的时候,定义的只是一个引用,外面可以通过这个引用来访问这个类里面的属性和方法. 那们类里面是够也应该有一个引用来访问自己的属性和方法纳? 呵呵,JAVA提供了一个很好的东西,就是 t ...

  6. (十四)java中super和this

    super代表的是父类.超类,用在继承中的子类中:this代表对象本身,用在本类中.     super访问的是被子类隐藏的父类的属性或被覆盖的方法,而this访问的是同一类中的成员.     sup ...

  7. 并发与高并发(四)-java并发的优势和风险

  8. 聊聊高并发(三十四)Java内存模型那些事(二)理解CPU快速缓存的工作原理

    在上一篇聊聊高并发(三十三)从一致性(Consistency)的角度理解Java内存模型 我们说了Java内存模型是一个语言级别的内存模型抽象.它屏蔽了底层硬件实现内存一致性需求的差异,提供了对上层的 ...

  9. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...

随机推荐

  1. jmock2.5 基本教程

    目录 第0章 概述 第1章 jmock初体验 第2章 期望 第3章 返回值 第4章 参数匹配 第5章 指定方法调用次数 第6章 指定执行序列 第7章 状态机 第0章 概述 现在的dev不是仅仅要写co ...

  2. Executor

    一.为什么需要Executor?为了更好的控制多线程,JDK提供了一套线程框架Executor,帮助开发人员有效的进行线程控制.他们都在java.util.concurrent包中,是JDK并发包的核 ...

  3. Visual Studio Code 常用插件整理

    常用插件说明: 一.HTML Snippets 超级使用且初级的H5代码片段以及提示 二.HTML CSS Support  让HTML标签上写class智能提示当前项目所支持的样式 三.Debugg ...

  4. mysql千万级表关联优化

    MYSQL一次千万级连表查询优化(一) 概述: 交代一下背景,这算是一次项目经验吧,属于公司一个已上线平台的功能,这算是离职人员挖下的坑,随着数据越来越多,原本的SQL查询变得越来越慢,用户体验特别差 ...

  5. Java 中byte 与 char 的相互转换 Java基础 但是很重要

    char转化为byte: public static byte[] charToByte(char c) {        byte[] b = new byte[2];        b[0] = ...

  6. nginx配置web服务器

    一:设置虚拟服务器 1.设置 http { server { listen 127.0.0.1:8080; server_name example.org www.example.org; } } 2 ...

  7. iOS开发之app打包发布流程

    一.准备工作 苹果开发者中心 1.申请苹果开发者账号 首先需要申请苹果开发者账号才能在APP store 里发布应用. 开发者账号分类:(1)个人开发者账号 (2)企业开发者账号 主要的区别是:点击这 ...

  8. 基于 Laravel 开发博客应用系列 —— 项目必备软件安装

    1.概述 通过本项目我们将会构建一个简单.清爽.优雅的博客系统,以及维护管理该博客的后台. 本项目源码公开在GitHub上:https://github.com/ChuckHeintzelman/l5 ...

  9. Outlook数据提取工具readpst

    Outlook数据提取工具readpst   Outlook是Windows常用的邮件客户端.它将用户的信息保存到.pst文件中,如邮件.约会.日历.联系人等信息.为了便于查看这些信息,Kali Li ...

  10. 1035 Password (20)(20 point(s))

    problem To prepare for PAT, the judge sometimes has to generate random passwords for the users. The ...