- 浏览: 40395 次
- 性别:
- 来自: 上海
文章分类
最新评论
Java监视器支持两种线程:互斥和协作
。
前面我们介绍了采用对象锁和重入锁来实现的互斥。这一篇中,我们来看一看线程的协作。
举个例子:有一家汉堡店举办吃汉堡比赛,决赛时有3个顾客来吃,3个厨师来做,一个服务员负责协调汉堡的数量。为了避免浪费,制作好的汉堡被放进一个能装
有10个汉堡的长条状容器中,按照先进先出的原则取汉堡。如果容器被装满,则厨师停止做汉堡,如果顾客发现容器内的汉堡吃完了,就可以拍响容器上的闹铃,
提醒厨师再做几个汉堡出来。此时服务员过来安抚顾客,让他等待。而一旦厨师的汉堡做出来,就会让服务员通知顾客,汉堡做好了,让顾客继续过来取汉堡。
这里,顾客其实就是我们所说的消费者,而厨师就是生产者。容器是决定厨师行为的监视器,而服务员则负责监视顾客的行为。
在JVM中,此种监视器被称为等待并唤醒监视器
。
在这种监视器中,一个已经持有该监视器的线程 ,可以通过调用 监视对象的wait方法 ,暂停 自身的执行,并释放监视器 ,自己进入一个等待区 ,直到监视器内的 其他线程调用 了监视对象的notify方法 。当一个线程调用唤醒 命令以后,它会持续持有监视器,直到它主动释放监视器。而这之后,等待线程会苏醒,其中的 一个会重新获得监视器,判断条件状态,以便决定是否继续进入等待状态或者执行监视区域,或者退出。
package sky.cn.test4; public class NotifyTest { private String flag = "true"; class NotifyThread extends Thread { public NotifyThread (String name) { super(name); } public void run() { try { sleep(3000); //延迟3秒通知 } catch (InterruptedException e) { e.printStackTrace(); } flag = "false"; flag.notify(); } } class WaitThread extends Thread { public WaitThread(String name) { super(name); } public void run() { while (flag != "false") { System.out.println(getName() + " begin waiting!"); long startTime = System.currentTimeMillis(); try { flag.wait(); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(getName() + " wai time: " + (endTime - startTime)); } System.out.println(getName() + " end waiting!"); } } public static void main(String[] args) { System.out.println("Main Thread Run!"); NotifyTest test = new NotifyTest(); NotifyThread notifyThread = test.new NotifyThread("notify01"); WaitThread waitThread01 = test.new WaitThread("waitThread01"); WaitThread waitThread02 = test.new WaitThread("waitThread02"); WaitThread waitThread03 = test.new WaitThread("waitThread03"); notifyThread.start(); waitThread01.start(); waitThread02.start(); waitThread03.start(); } }
这段代码启动了三个 简单的wait线程 ,当他们处于等待状态以后,试图由一个notify线程 来唤醒。
运行这段程序,你会发现,满屏的java.lang.IllegalMonitorStateException ,根本不是你想要的结果。
请注意以下几个事实:
1. 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
2. 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)。
3. 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常
。
4. JVM基于多线程,默认情况下不能保证运行时线程的时序性。
也就是说,当线程在调用某个对象的wait或者notify方法的时候,要先取得该对象的控制权,换句话说,就是进入这个对象的监视器。
通过前面对同步的讨论,我们知道,要让一个线程进入某个对象的监视器 ,通常有三种方法 :
1: 执行对象的某个同步实例方法
2: 执行对象对应的同步静态方法
3: 执行对该对象加同步锁的同步块
显然,在上面的例程中,我们用第三种方法比较合适。
于是我们将上面的wait和notify方法调用包在同步块中。
synchronized (flag) { flag = "false"; flag.notify(); }
synchronized (flag) { while (flag != "false") { System.out.println(getName() + " begin waiting!"); long startTime = System.currentTimeMillis(); try { flag.wait(); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(getName() + " wai time: " + (endTime - startTime)); } System.out.println(getName() + " end waiting!"); }
但是,运行这个程序,我们发现事与愿违。那个非法监视器异常 又出现了。。。
我们注意到,针对flag的同步块 中,我们实际上已经更改了flag对对象的引用: flag="false";
显然,这样一来,同步块也无能为力了,因为我们根本不是针对唯一的一个对象在进行同步 。
我们不妨将flag封装到JavaBean或者数组中去,这样用JavaBean对象或者数组对象进行同步 ,就可以达到既能修改里面参数又不耽误同步的目的。
private String[] flag = {"true"};
synchronized (flag) { flag[0] = "false"; flag.notify(); }
synchronized (flag) { while (flag[0] != "false") { System.out.println(getName() + " begin waiting!"); long startTime = System.currentTimeMillis(); try { flag.wait(); } catch (InterruptedException e) { e.printStackTrace(); } long endTime = System.currentTimeMillis(); System.out.println(getName() + " wait time: " + (endTime - startTime)); } System.out.println(getName() + " end waiting!"); }
运行这个程序,看不到异常了
。但是仔细观察结果,貌似只有一个线程被唤醒
。利用jconsole等工具查看线程状态,发现的确还是有两个线程被阻塞的。这是为啥呢?
程序中使用了flag.notify()方法
。只能是随机的唤醒一个线程
。我们可以改用flag.notifyAll()方法
。这样,所有被阻塞的线程都会被唤醒了。
最终代码请读者自己修改,这里不再赘述。
好了,亲爱的读者们,让我们回到开篇提到的汉堡店大赛问题当中去,来看一看厨师、服务生和顾客是怎么协作进行这个比赛的。
首先我们构造故事中的三个次要对象:汉堡包、存放汉堡包的容器、服务生
package sky.cn.test4; /** * 服务生,配角,不需要属性 */ public class Waiter { }
package sky.cn.test4; /** * 汉堡包 */ public class Hamberg { private int id; //汉堡编号 private String cookerId; //厨师编号 public Hamberg(int id, String cookerId) { this.id = id; this.cookerId = cookerId; System.out.println(this.toString()); } public String toString() { return "#hamberg " + id + "c" + cookerId + " makes by cooker " + cookerId; } }
package sky.cn.test4; import java.util.ArrayList; import java.util.List; /** * 汉堡包容器 */ public class HambergFifo { List<Hamberg> hambergs = new ArrayList<Hamberg>(); int maxSize = 10; //放入汉堡 public <T extends Hamberg> void push(T t) { hambergs.add(t); } //取出汉堡 public Hamberg pop() { Hamberg h = hambergs.get(0); hambergs.remove(0); return h; } //判断容器是否为空 public synchronized boolean isEmpty() { return hambergs.isEmpty(); } //判断容器内汉堡的个数 public synchronized int size() { return hambergs.size(); } //返回窗口的最大容量 public synchronized int getMaxSize() { return this.maxSize; } //判断容器是否已满,未满为真 public synchronized boolean isNotFull() { return hambergs.size() < this.maxSize; } }
接下来我们构造厨师对象:
package sky.cn.test4; /** * 厨师 */ public class Cooker implements Runnable { HambergFifo pool; //厨师要面对容器 Waiter waiter; //还要面对服务生 public Cooker(Waiter waiter, HambergFifo pool) { this.pool = pool; this.waiter = waiter; } //制造汉堡 public void makeHamberg() { //制造的个数 int madeCount = 0; //因为容器满,被迫等待的次数 int fullFiredCount = 0; String threadName = Thread.currentThread().getName(); try { while (true) { Thread.sleep(1000); synchronized (pool) { if (pool.isNotFull()) { synchronized (waiter) { //容器未满, 制作汉堡, 并放入容器 pool.push(new Hamberg(++madeCount, threadName)); //说出容器内汉堡数量 System.out.println(threadName + ": There are " + pool.size() + " hambergs in all"); //让服务生通知顾客,有汉堡可以吃了 waiter.notifyAll(); System.out.println("### Cooker: waiter.notifyAll(): " + " Hi!Customers, we got some new hambergs."); } } else { if (fullFiredCount++ < 10) { //发现容器满了,停止做汉堡的尝试 System.out.println(threadName + " : Hamberg Pool is Full," + " stop making hamberg"); System.out.println("### Cooker: pool.wait()"); //汉堡容器的状况使厨师等待 pool.wait(); } else { return ; } } } //做完汉堡要进行收尾工作,为下一次的制作做准备 Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); madeCount--; } } public void run() { makeHamberg(); } }
接下来,我们构造顾客对象:
package sky.cn.test4; import java.util.Random; /** * 顾客 */ public class Customer implements Runnable { Waiter waiter; //顾客要面对服务生 HambergFifo pool; //也要面对汉堡包容器 int ateCount = 0; //想要记下自己吃了多少汉堡 long sleepTime; //吃每个汉堡的时间不尽相同 Random r = new Random(); //用于产生随机数 public Customer(Waiter waiter, HambergFifo pool) { this.waiter = waiter; this.pool = pool; } public void run() { while (true) { try { //取汉堡 getHamberg(); //吃汉堡 eatHamberg(); } catch (Exception e) { synchronized (waiter) { System.out.println(e.getMessage()); //若取不到汉堡,要和服务生打交道 try { System.out.println("###Customer: waiter.wait():" + " Sorry, sir, there is no hambergs left, please wait"); System.out.println(Thread.currentThread().getName() + ": OK, waiting for a new hamberg"); //服务生安抚客户,让他等待 waiter.wait(); continue; } catch (InterruptedException ie) { ie.printStackTrace(); } } } } } private void eatHamberg() { try { //吃每个汉堡的时间不等 sleepTime = Math.abs(r.nextInt(3000)) * 5; System.out.println(Thread.currentThread().getName() + " : I`m eating the hamberg for " + sleepTime + " milliseconds"); Thread.sleep(sleepTime); } catch (Exception e) { e.printStackTrace(); } } private void getHamberg() { Hamberg hamberg = null; synchronized (pool) { try { //从容器内取汉堡 hamberg = pool.pop(); ateCount++; System.out.println(Thread.currentThread().getName() + ": I got " + ateCount + "th hamberg " + hamberg); System.out.println(Thread.currentThread().getName() + ": There are still " + pool.size() + " hambergs left"); } catch (Exception e) { pool.notifyAll(); System.out.println("### Customer: pool.notifyAll()"); throw new RuntimeException(Thread.currentThread().getName() + ": Oh my god!!! No hambergs left, waiter!" + " [Ring the bell besides the hamberg pool]"); } } } }
最后,我们构造汉堡店,让这个故事发生:
package sky.cn.test4; public class HambergShop { Waiter waiter = new Waiter(); HambergFifo hambergPool = new HambergFifo(); Customer customer = new Customer(waiter, hambergPool); Cooker cooker = new Cooker(waiter, hambergPool); public static void main(String[] args) { HambergShop hambergShop = new HambergShop(); Thread t1 = new Thread(hambergShop.customer, "1"); Thread t2 = new Thread(hambergShop.customer, "2"); Thread t3 = new Thread(hambergShop.customer, "3"); Thread t4 = new Thread(hambergShop.cooker, "1"); Thread t5 = new Thread(hambergShop.cooker, "2"); Thread t6 = new Thread(hambergShop.cooker, "3"); t4.start(); t5.start(); t6.start(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } t1.start(); t2.start(); t3.start(); } }
运行这个程序吧,然后你会看到我们汉堡店的比赛进行的很好,只是不知道那些顾客是不是会被撑到。。。
读到这里,有的读者可能会想到前面介绍的重入锁ReentrantLock。
有的读者会问:如果我用ReentrantLock来代替上面这些例程当中的
synchronized块,是不是也可以呢?感兴趣的读者不妨一试。
但是在这里,我想提前给出结论,就是,
如果用ReentrantLock的lock()和unlock()方法代替上面的synchronized块
,那么上面这些程序还是要抛出
java.lang.IllegalMonitorStateException异常
的,不仅如此,你甚至还会看到线程死锁
。原因就是当某个线程调用第三
方对象的wait或者notify方法的时候,并没有进入第三方对象的监视器,于是抛出了异常信息
。但此时,程序流程如果没有用finally来处理
unlock方法,那么你的线程已经被lock方法上锁,并且无法解锁。程序在java.util.concurrent框架的语义级别死锁了
,你用
JConsole这种工具来检测JVM死锁,还检测不出来。
正确的做法就是,只使用ReentrantLock,而不使用wait或者notify方法 。因为ReentrantLock已经对这种互斥和协作进行了概括。所以,根据你程序的需要,请单独采用重入锁或者synchronized一种同步机制,最好不要混用 。
好了,我们现在明白:
1. 线程的等待或者唤醒,并不是让线程调用自己的wait或者notify方法,而是通过调用线程共享对象的wait或者notify方法来实现。
2. 线程要调用某个对象的wait或者notify方法,必须先取得该对象的监视器。
3. 线程的协作必须以线程的互斥为前提,这种协作实际上是一种互斥下的协作
。
下一讲当中,我们来看看如何实实在在的解决线程之间抢占共享资源的问题。敬请期待!
来源: http://www.blogjava.net/zhangwei217245/archive/2010/04/24/316526.html
发表评论
-
Java 多线程同步问题的探究(三、Lock来了,大家都让开【2. Fair or Unfair? It is a question...】)
2012-08-15 16:12 534让我们继续前面有关ReentrantLock的话题。 首先, ... -
Java 多线程同步问题的探究(五、你有我有全都有—— ThreadLocal如何解决并发安全性?)【更新重要补疑】
2012-08-15 15:17 669前面我们介绍了Java当中 ... -
Java 多线程同步问题的探究(三、Lock来了,大家都让开【1. 认识重入锁】)
2012-08-13 16:44 837在上一节中, 我们已经了解了Java多线程编程中常用的关 ... -
Java 多线程同步问题的探究(二、给我一把锁,我能创造一个规矩)
2012-08-13 15:09 655在上一篇中,我们讲到 ... -
Java多线程同步问题的探究(一、线程的先来后到)
2012-08-13 14:46 431众所周知,在Java多线程编程中,一个非常重要的方面就是线程的 ... -
ThreadLocal源码读后感总结
2012-04-23 14:47 9541.关联类 ThreadLocal: 线程局 ...
相关推荐
g: 利用互斥量来解决线程同步互斥问题 h: problem1 生产者消费者问题 (1生产者 1消费者 1缓冲区) problem1 more 生产者消费者问题 (1生产者 2消费者 4缓冲区) problem2 读者与写着问题 I: 信号量 semaphore ...
本代码是用JAVA实现的生产者与消费者的问题,线程间的同步与互斥功能
操作系统实验 多线程同步与互斥 java编写 可动态创建
解决多线程编程中的同步互斥问题
java多线程同步互斥访问实例,对于初学者或是温故而知新的同道中人都是一个很好的学习资料
用java实现多线程并发中的读者与写者问题,能够实现多线程对临界资源的同步有序访问。 具体实现为: 给定一个队列A[1-10][1-100000]、元素编号1-10,其中每个元素包含10万个随机数。创建若干个线程,各循环100次;...
wait set——线程的休息室 wait方法——把线程放入wait set notify方法——从wait set拿出线程 notifyAll方法——从wait set拿出所有线程 wait、notify、notifyAll是Object类的方法 线程的状态移转 跟线程有关的其他...
Java多线程同步具体实例讲解 .doc
(1) 通过编写程序实现进程同步和互斥,掌握有关进程(线程)同步与互斥的原理,以及解决进程(线程)同步和互斥的算法,从而进一步巩固进程(线程)同步和互斥等有关的内容。 (2) 了解Windows2000/XP中多线程的...
在linux上分别用多进程和多线程实现的同步互斥操作(源代码)
通过JAVA多线程同步和互斥的技术实现CSMA/CD协议的模拟
C#的多线程同步,C#中四种进程或线程同步互斥的控制方法
java多线程小程序实例 java多线程小程序实例
本例将模仿经典的线程同步互斥例子——生产者和消费者问题,来演示 java 强大的多线程机制。生产者和消费者共享一个数据,当数据为0 时,消费者不可访问,生产者可访问数据,每次访问数据加1;当数据到达100 时,...
java ATM存取一体机(线程同步互斥) java ATM存取一体机(线程同步互斥)
在《秒杀多线程系列》的前十五篇中介绍多线程的相关概念,多线程同步互斥问题《秒杀多线程第四篇一个经典的多线程同步问题》及解决多线程同步互斥的常用方法——关键段、事件、互斥量、信号量、读写锁。为了让大家...
Linux下多线程及多进程及同步与互斥编程详细介绍
windows多线程的同步和互斥
C#.NET多线程实例6个(包括多线程基本使用,多线程互斥等全部多线程使用实例)