Java 并行 (2): Monitor
转自:http://www.cnblogs.com/tomsheep/archive/2010/06/09/1754419.html
1. 什么是Monitor?
Monitor其实是一种同步工具,也可以说是一种同步机制,它通常被描述为一个对象,主要特点是:
- 对象的所有方法都被“互斥”的执行。好比一个Monitor只有一个运行“许可”,任一个线程进入任何一个方法都需要获得这个“许可”,离开时把许可归还。
- 通常提供singal机制:允许正持有“许可”的线程暂时放弃“许可”,等待某个谓词成真(条件变量),而条件成立后,当前进程可以“通知”正在等待这个条件变量的线程,让他可以重新去获得运行许可。
Monitor对象可以被多线程安全地访问。关于“互斥”与“为什么要互斥”,我就不傻X兮兮解释了;而关于Monitor的singal机制,历史上曾经出现过两大门派,分别是Hoare派和Mesa派(上过海波老师OS课的SS同学应该对这个有印象),我还是用我的理解通俗地庸俗地解释一下:
- Hoare派的singal机制江湖又称“Blocking condition variable”,特点是,当“发通知”的线程发出通知后,立即失去许可,并“亲手”交给等待者,等待者运行完毕后再将许可交还通知者。在这种机制里,可以等待者拿到许可后,谓词肯定为真——也就是说等待者不必再次检查条件成立与否,所以对条件的判断可以使用“if”,不必“while”
- Mesa派的signal机制又称“Non-Blocking condition variable”, 与Hoare不同,通知者发出通知后,并不立即失去许可,而是把闻风前来等待者安排在ready queue里,等到schedule时有机会去拿到“许可”。这种机制里,等待者拿到许可后不能确定在这个时间差里是否有别的等待者进入过Monitor,因此不能保证谓词一定为真,所以对条件的判断必须使用“while”
这两种方案可以说各有利弊,但Mesa派在后来的盟主争夺中渐渐占了上风,被大多数实现所采用,有人给这种signal另外起了个别名叫“notify”,想必你也知道,Java采取的就是这个机制。
2. Monitor与Java不得不说的故事
子曰:“Java对象是天生的Monitor。”每一个Java对象都有成为Monitor的“潜质”。这是为什么?因为在Java的设计中,每一个对象自打娘胎里出来,就带了一把看不见的锁,通常我们叫“内部锁”,或者“Monitor锁”,或者“Intrinsic lock”。为了装逼起见,我们就叫它Intrinsic lock吧。有了这个锁的帮助,只要把类的所有对象方法都用synchronized关键字修饰,并且所有域都为私有(也就是只能通过方法访问对象状态),就是一个货真价实的Monitor了。比如,我们举一个大俗例吧:

public class Account {
private int balance; public Account(int balance) {
this.balance = balance;
} synchronized public boolean withdraw(int amount){
if(balance<amount)
return false;
balance -= amount;
return true;
} synchronized public void deposit(int amount){
balance +=amount;
} }
3. synchronized关键字
上面我们已经看到synchronized的一种用法,用来修饰方法,表示进入该方法需要对Intrinsic lock加锁,离开时放锁。synchronized可以用在程序块中,显示说明对“哪个对象的Intrinsic lock加锁”,比如

synchronized public void deposit(int amount){
balance +=amount;
}
// 等价于
public void deposit(int amount){
synchronized(this){
balance +=amount;
}
}
这时,你可能就要问了,你不是说任何对象都有intrinsic lock么?而synchronized关键字又可以显示指定去锁谁,那我们是不是可以这样做:

public class Account {
private int balance;
private Object lock = new Object(); public Account(int balance) {
this.balance = balance;
} public boolean withdraw(int amount){
synchronized (lock) {
if(balance<amount)
return false;
balance -= amount;
return true;
}
} public void deposit(int amount){
synchronized (lock) {
balance +=amount;
}
}
}
不用this的内部锁,而是用另外任意一个对象的内部锁来完成完全相同的任务?没错,完全可以。不过,需要注意的是,这时候,你实际上禁止了“客户代码加锁”的行为。前几天BBS上简哥有一贴提到的bug其实就是这个,这个时候使用这份代码的客户程序如果想当然地认为Account的同步是基于其内部锁的,并且傻X兮兮地写了类似下面的代码:

public static void main(String[] args) {
Account account =new Account(1000); //some threads modifying account through Account’s methods... synchronized (account) {
;//blabla
}
}
自认为后面的同步快对account加了锁,期间的操作不会被其余通过Account方法操作account对象的线程所干扰,那就太悲剧了。因为他们并不相干,锁住了不同的锁。
4. Java中的条件变量
正如我们前面所说,Java采取了wait/notify机制来作为intrinsic lock 相关的条件变量,表示为等待某一条件成立的条件队列——说到这里顺带插一段,条件队列必然与某个锁相关,并且语义上关联某个谓词(条件队列、锁、条件谓词就是吉祥的一家)。所以,在使用wait/notify方法时,必然是已经获得相关锁了的,在进一步说,一个推论就是“wait/notify 方法只能出现在相应的同步块中”。如果不呢?就像下面一段(notify表示的谓词是“帐户里有钱啦~”):

public void deposit(int amount){
balance +=amount;
notify();
} //或者这样: public void deposit(int amount){
synchronized (lock) {
balance +=amount;
notify();
}
}
这两段都是错的,第一段没有在同步块里,而第二段拿到的是lock的内部锁,调用的却是this.notify(),让人遗憾。运行时他们都会抛IllegalMonitorStateException异常——唉,想前一阵我参加一次笔试的时候,有一道题就是这个,让你选所给代码会抛什么异常,我当时就傻了,想这考得也太偏了吧,现在看看,确实是很基本的概念,当初被虐是压根没有理解wait/notify机制的缘故。那怎么写是对的呢?

public void deposit(int amount){
synchronized (lock) {
balance +=amount;
lock.notify();
}
}
//或者(取决于你采用的锁):
synchronized public void deposit(int amount){
balance +=amount;
notify();
}
5.这就够了吗?
看上去,Java的内部锁和wait/notify机制已经可以满足任何同步需求了,不是吗?em…可以这么说,但也可以说,不那么完美。有两个问题:
- 锁不够用
有时候,我们的类里不止有一个状态,这些状态是相互独立的,如果只用同一个内部锁来维护他们全部,未免显得过于笨拙,会严重影响吞吐量。你马上会说,你刚才不是演示了用任意一个Object来做锁吗?我们多整几个Object分别加锁不就行了吗?没错,是可行的。但这样可能显得有些丑陋,而且Object来做锁本身就有语义不明确的缺点。
- 条件变量不够用
Java用wait/notify机制实际上默认给一个内部锁绑定了一个条件队列,但是,有时候,针对一个状态(锁),我们的程序需要两个或以上的条件队列,比如,刚才的Account例子,如果某个2B银行有这样的规定“一个账户存款不得多于10000元”,这个时候,我们的存钱需要满足“余额+要存的数目不大于10000,否则等待,直到满足这个限制”,取钱需要满足“余额足够,否则等待,直到有钱为止”,这里需要两个条件队列,一个等待“存款不溢出”,一个等待“存款足够”,这时,一个默认的条件队列够用么?你可能又说,够用,我们可以模仿network里的“多路复用”,一个队列就能当多个来使,像这样:

public class Account {
public static final int BOUND = 10000;
private int balance; public Account(int balance) {
this.balance = balance;
} synchronized public boolean withdraw(int amount) throws InterruptedException{
while(balance<amount)
wait();// no money, wait
balance -= amount;
notifyAll();// not full, notify
return true;
} synchronized public void deposit(int amount) throws InterruptedException{
while(balance+amount >BOUND)
wait();//full, wait
balance +=amount;
notifyAll();// has money, notify
}
}
不是挺好吗?恩,没错,是可以。但是,仍然存在性能上的缺陷:每次都有多个线程被唤醒,而实际只有一个会运行,频繁的上下文切换和锁请求是件很废的事情。我们能不能不要notifyAll,而每次只用notify(只唤醒一个)呢?不好意思,想要“多路复用”,就必须notifyAll,否则会有丢失信号之虞(不解释了)。只有满足下面两个条件,才能使用notify:
一,只有一个条件谓词与条件队列相关,每个线程从wait返回执行相同的逻辑。
二,一进一出:一个对条件变量的通知,语义上至多只激活一个线程。
我又想插播一段:刚才写上面那段代码,IDE提示抛InterruptedException,我想提一下,这是因为wait是一个阻塞方法,几乎所有阻塞方法都会声明可能抛InterruptedException,这是和Java的interrupt机制有关的,以后我们有机会再说。
既然这么做不优雅不高效不亚克西,那如之奈何?Java提供了其他工具吗?是的。这就是传说中的java.util.concurrency包里的故事,今天也不说了,有机会在和大家讨论。
主要参考资料:
1. Wiki
2. Addison Wesley, Java Concurrency in Practice ,Brian Goetz
Java 并行 (2): Monitor的更多相关文章
- 探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现]
探索 Java 同步机制[Monitor Object 并发模式在 Java 同步机制中的实现] https://www.ibm.com/developerworks/cn/java/j-lo-syn ...
- Java 并行与并发
Java 并行与并发 注意两个词:并行(Concurrent) 并发(Parallel) 并行:是逻辑上同时发生,指在某一个时间内同时运行多个程序 并发:是物理上同时发生,指在某一个时间点同时运行多个 ...
- Java并发程序设计(二)Java并行程序基础
Java并行程序基础 一.线程的生命周期 其中blocked和waiting的区别: 作者:赵老师链接:https://www.zhihu.com/question/27654579/answer/1 ...
- JAVA并行程序基础
JAVA并行程序基础 一.有关线程你必须知道的事 进程与线程 在等待面向线程设计的计算机结构中,进程是线程的容器.我们都知道,程序是对于指令.数据及其组织形式的描述,而进程是程序的实体. 线程是轻量级 ...
- 杂项-Java:Druod Monitor
ylbtech-杂项-Java:Druid Monitor 1.返回顶部 1. https://www.cnblogs.com/wanghuijie/p/druid_monitor.html 2. 2 ...
- JAVA并行程序基础二
JAVA并行程序基础二 线程组 当一个系统中,如果线程较多并且功能分配比较明确,可以将相同功能的线程放入同一个线程组里. activeCount()可获得活动线程的总数,由于线程是动态的只能获取一个估 ...
- JAVA并行程序基础一
JAVA并行程序基础一 线程的状态 初始线程:线程的基本操作 1. 新建线程 新建线程只需要使用new关键字创建一个线程对象,并且用start() ,线程start()之后会执行run()方法 不要直 ...
- Java:并行编程及同步使用方法
知道java可以使用java.util.concurrent包下的 CountDownLatch ExecutorService Future Callable 实现并行编程,并在并行线程同步时,用起 ...
- JAVA并行框架:Fork/Join
一.背景 虽然目前处理器核心数已经发展到很大数目,但是按任务并发处理并不能完全充分的利用处理器资源,因为一般的应用程序没有那么多的并发处理任务.基于这种现状,考虑把一个任务拆分成多个单元,每个单元分别 ...
随机推荐
- ACdream区域赛指导赛之专题赛系列(1)の数学专场
Contest : ACdream区域赛指导赛之专题赛系列(1)の数学专场 A:EOF女神的相反数 题意:n(<=10^18)的数转化成2进制.翻转后(去掉前导零)输出十进制 思路:water ...
- pthread_rwlock pthread读写锁
原文: http://www.cnblogs.com/diegodu/p/3890450.html 使用读写锁 配置读写锁的属性之后,即可初始化读写锁.以下函数用于初始化或销毁读写锁.锁定或解除锁定读 ...
- 让devstack中的vm訪问外网
devstack默认会建立一个Public网络,地址为172.24.4.0/24,可是这个网络并非运营商分配给我们的网络.所以仅仅能通过nat的方式让devstack建立的虚拟机訪问外网. br-ex ...
- android之GMS认证
来到了新的公司,才知道做手机是须要做GMS认证的.于是从一个从没有做过GMS认证的小白到一个月做了8个项目的GMS认证.最后.自己都是吐了.每天晚上都是一个人傻傻在加班.更是知道了高通的支持力度让人发 ...
- iOS APP开发概述----学习笔记001
之前开发过一些Android APP,如今開始学习iOS开发,未来实际工作应该会用到.未雨绸缪. 一.了解其系统层次架构 其系统分层四层,其具体例如以下: 第一层:Core OS watermark/ ...
- ios单元測试之GHUnit
1.相同创建一个測试的project, 2.通过cocoaPod来下载GHUnit框架,或者到github上下载.由于这个框架是开源的第三方框架. 同一时候加入QuartCore.framework( ...
- vim中凝视多行python代码
在vim中凝视多行python代码比較麻烦,主要由下面几种方法: (1)将须要凝视的代码以文档字符串的形式呈现 (2)将须要凝视的代码以函数的形式呈现 (3)使用vim自身快捷键 我们主要使用第三种方 ...
- 深入理解Android开发中的CoordinatorLayout Behavior
在使用Android设计支持库(Android Design Support Library)时,很难避开CoordinatorLayout:设计库中有很多视图都需要CoordinatorLayout ...
- Bootstrap 只读输入框
只读输入框 为输入框设置 readonly 属性可以禁止用户输入,并且输入框的样式也是禁用状态. <input class="form-control" type=&qu ...
- jorgchart,帮助你生成组织结构图的
下载地址: http://yunpan.cn/c6pfenkmmFV2q 访问密码 8e29 演示链接: http://www.gbtags.com/gb/share/546.htm jstree. ...