Java并发编程之(二)管程
本博客转载自《奔跑的猪的博客》
本文中的代码可以在github上找到,有需要的请自行下载
标题
定义
维基百科中定义管程为:在并发编程中,管程(monitor)为一个同步结构,具有线程互斥特性,以及能够根据某些条件来阻塞线程。根据定义,管程有三个要素:同步、互斥、条件。恰好在Java的Concurrent包中ReentrantLock具有上述所有特性,可以用来实现管程。管程是一个非常实用且常见的技术,可以用来实现很多常用的并发数据结构,例如阻塞队列。
阻塞队列
队列常用于生产者消费者模型,生产者发送消息并存储到队列,消费者从队列中取出消息。一般情况下会存在多个生产者和多个消费者,普通的队列不能保证并发的安全,因此需要用到线程安全的技术。线程安全的队列又可以分为两种:
- 阻塞队列(BlockingQueue)
- 非阻塞队列(NoBlockingQueue)
阻塞队列的特征为,当队列为空时会阻塞消费者,当队列满时阻塞生产者。这种机制能够平衡生产者和消费者的负载。
分析上述定义,阻塞队列具有线程安全,阻塞,条件的特性;线程安全和阻塞就意味着要实现同步以及互斥,因此,管程能够很好地满足阻塞队列的要求。
Java源码中的管程
如下代码所示是Concurrent包中ArrayBlockingQueue的实现,队列有两种实现方式,数组和链表。Array是数组的实现,而数组通常是有界的。
ArrayBlockingQueue结构将ReentrantLock(可重入锁)作为全局锁来实现线程安全,所谓全局锁就是整个数据结构中就这么一个锁,当一个线程在访问该对象并获得锁之后,其他线程要访问该对象都得阻塞。在后续的学习中,我们会发现全局锁有一定的弊端,因为他锁住了整个对象,使得整体的并发性不高。源代码中的newCondition()操作会生成一个条件对象,条件对象具有唤醒线程和挂起线程的能力,具体的操作如第二段代码所示。
public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition();
}
第二段代码是ArrayBlockingQueue的put操作,put操作的第一步就是获得全局锁,然后判断队列当前元素是否已满,如果队列慢就会使用notFull条件变量将线程挂起,否则就会调用enqueue函数进行入队操作。入队操作同时会使用notEmpty条件变量来唤醒一个被notEmpty阻塞的的线程(take操作会调用notEmpty来阻塞线程)。
public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();//挂起线程,当其他线程调用notFull.signal()或者notFull.signlAll()时,该线程唤醒。enqueue(e);} finally {lock.unlock();}}
private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();//唤醒一个被notEmpty阻塞的线程,因为此时队列已经非空了}
实例
下面我为了熟悉管程的使用,会亲自造轮子体会一下。
package Monitors;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;public class BlockingQueue<E> {private ReentrantLock lock;//全局锁private Condition notFull;//条件变量,用来监控队列是否已满private Condition notEmpty;//条件变量,用来监控队列是否为空private final int capacity;//容量,这是一个定容的队列private int head;//队列头部哨兵private int tail;//队列尾部哨兵private int count;//当然元素个数private E[] data;//保存元素的数组/*初始化阻塞队列*/public BlockingQueue(int c){this.lock=new ReentrantLock();this.notEmpty=lock.newCondition();this.notFull=lock.newCondition();this.capacity=c;this.count=0;this.head=0;this.tail=0;data=(E[])new Object[c+1];}/*向队列中添加一个元素*/public void put(E e){lock.lock();try{while(this.count==this.capacity)//判断队列是否已满notFull.await();//挂起线程tail++;count++;if(tail==this.capacity+1)tail=0;data[tail]=e;//插入元素notEmpty.signal();//通知其他线程,队列不为空} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}finally{lock.unlock();}}/*获取队列容量*/public int getCap(){return this.capacity;}/*判读队列是否为空*/public boolean isEmpty(){lock.lock();try{return count==0?true:false;}finally{lock.unlock();}}/*判断队列是否已满*/public boolean isFull(){lock.lock();try{return count==capacity?true:false;}finally{lock.unlock();}}/*从队列中取出一个元素*/public E take() throws InterruptedException{lock.lock();try{while(this.count==0)//判断队列是否为空notEmpty.await();//阻塞队列head++;count--;if(head==this.capacity+1)head=0;E x=data[head];//获得头部元素notFull.signal();//通知其他线程,队列未满return x;}finally{lock.unlock();}}
}
在上锁所有操作中,几乎每个操作都在如下的结构体中进行,为了防止代码异常退出而导致锁没有被释放,必须使用finally关键字确保锁的释放。该代码块的作用和synchronized同步块作用类似,在进入同步块之前都要先获得锁,然后进行同步操作;在退出同步块的时候都要释放锁并再次同步。
lock.lock();
try{return count==capacity?true:false;
}finally{lock.unlock();
}
参考文献
下面两本书我极力推荐,它们对并发编程解释得非常全面。
[1] Brian Goetz等著 童云兰译..机械工业出版社
[2][Maurice Herlihy, Nir Shavit著,<多处理器编程的艺术]
Java并发编程之(二)管程相关推荐
- [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors...
[Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...
- java并发编程(二十一)----(JUC集合)CopyOnWriteArraySet和ConcurrentSkipListSet介绍
转载自 java并发编程(二十一)----(JUC集合)CopyOnWriteArraySet和ConcurrentSkipListSet介绍 这一节我们来接着介绍JUC集合:CopyOnWrite ...
- Java并发编程(二)- 分工
目录 分工问题 线程池 Java线程池的基本用法 线程池添加线程的逻辑 线程池的重要参数:工作队列 线程池的重要参数:拒绝策略 创建线程池的快捷方法 线程数量 线程池使用原则 获取线程执行结果 - F ...
- Java并发编程(二)同步
1. 锁对象 Synchronized synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C. D等)正在用这个方 ...
- java并发编程(二)synchronized
参考文章: http://blog.csdn.net/javazejian/article/details/72828483 http://ifeve.com/java-synchronized/ h ...
- Java 并发编程(二)对象的发布逸出和线程封闭
对象的发布与逸出 "发布(Publish)"一个对象是指使对象能够在当前作用域之外的代码中使用.可以通过 公有静态变量,非私有方法,构造方法内隐含引用 三种方式. 如果对象构造完成 ...
- java并发编程(二)多个线程多个锁
多个线程多个锁 多个线程多个锁:多个线程,每个线程都可以拿到自己制定的锁,分别获得锁之后,执行synchronized方法体的内容.就是在上次那个博客上说道的锁竞争的问题,是因为所有的线程过来以后都争 ...
- java并发编程学习一
java并发编程学习一 什么是进程和线程? 进程是操作系统进行资源分配的最小单位 进程跟进程之间的资源是隔离的,同一个进程之中的线程可以共享进程的资源. 线程是进程的一个实体,是CPU 调度和分派的基 ...
- Java并发编程(三)volatile域
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Android多线程(一)线程池 Android多线程(二)AsyncTask源代码分析 前言 有时仅仅为了读写一个或 ...
- Java并发编程最佳实例详解系列
Java并发编程最佳实例详解系列: Java并发编程(一)线程定义.状态和属性 Java并发编程(一)线程定义.状态和属性 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中, ...
最新文章
- lintcode 滑动窗口的最大值(双端队列)
- 开发者需要了解的WebKit
- 算法笔记——整数划分1
- 问题解决: 解释器错误: 没有那个文件或目录
- Scrapy框架的学习(9.Scrapy中的CrawlSpider类的作用以及使用,实现优化的翻页爬虫)
- java 数组长度 可变_java基础之集合长度可变的实现原理
- 洛谷 P1377 [TJOI2011]树的序 解题报告
- 动态规划求解所有字符的组合数
- UISegmentedControl UISlider
- 实践教程 | Pytorch 模型的保存与迁移
- android 图片 写入文件格式,android实现将位置信息写入JPEG图片文件
- 打造金刚不坏之心的秘籍
- java swing 汽车租赁管理系统 java swing mysql实现的汽车租赁管理系统源码(1027)
- 开始学习机器学习之前你必须要了解的知识有哪些?机器学习系列入门篇
- 将日/分钟数据转为周线、月线或其他周期
- linux docker启动指定字符集,【字符集】解决docker 容器中中文乱码问题
- Dynamics 365 窗体中设置可编辑的子网格
- rs_D455相机内外参标定+imu联合标定
- 【springboot进阶】RestTemplate 集成 okhttp3 请求带p12证书
- 光纤收发器的6个指示灯说明
热门文章
- 电脑怎么写入便签并同步到手机版便签上?
- 计算机一打开就卡在更新失败,做系统一直在正在启动画面-电脑开机后卡在“正在启动windows”界面,怎么办?...
- 汉语言专家级C1,汉语言文学专业审核(文科生均可参考)
- 北京/上海内推 | 小红书社区技术部招聘NLP/多模态算法工程师/实习生
- 破解EXCEL工作表保护密码
- web学习-项目练习-No.4-朋友圈
- linux中dd命令详解,Linux基础知识之dd命令详解
- linux dd查看磁盘读写,使用dd命令 测试磁盘读写速度
- 等级保护二、三、四级内容及对比
- OSChina 周一乱弹 ——强行把她拖到家里洗了个澡