JAVA线程同步 (一)wait(), notify()和notifyAll()使用
wait(),notify()和notifyAll()都是java.lang.Object的方法:
wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
notify(): Wakes up a single thread that is waiting on this object's monitor.
notifyAll(): Wakes up all threads that are waiting on this object's monitor.
这三个方法,都是Java语言提供的实现线程间阻塞(Blocking)和控制进程内调度(inter-process communication)的底层机制。在解释如何使用前,先说明一下两点:
1. 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(Condition queue)。而这个对象里的wait(), notify()和notifyAll()则是这个条件队列的固有(intrinsic)的方法。
2. 一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。
(An object's intrinsic lock and its intrinsic condition queue are related: in order to call any of the condition queue methods on object X, you must hold the lock on X. This is because the mechanism for waiting for state-based conditions is necessarily tightly bound to the mechanism fo preserving state consistency)
根据上述两点,在调用wait(), notify()或notifyAll()的时候,必须先获得锁,且状态变量须由该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。
知道怎么使用后,我们来问下面的问题:
1. 执行wait, notify时,不获得锁会如何?
请看代码:
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
obj.wait();
obj.notifyAll();
}
执行以上代码,会抛出java.lang.IllegalMonitorStateException的异常。
2. 执行wait, notify时,不获得该对象的锁会如何?
请看代码:

public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Object lock = new Object();
synchronized (lock) {
obj.wait();
obj.notifyAll();
}
}

执行代码,同样会抛出java.lang.IllegalMonitorStateException的异常。
3. 为什么在执行wait, notify时,必须获得该对象的锁?
这是因为,如果没有锁,wait和notify有可能会产生竞态条件(Race Condition)。考虑以下生产者和消费者的情景:
1.1生产者检查条件(如缓存满了)-> 1.2生产者必须等待
2.1消费者消费了一个单位的缓存 -> 2.2重新设置了条件(如缓存没满) -> 2.3调用notifyAll()唤醒生产者
我们希望的顺序是: 1.1->1.2->2.1->2.2->2.3
但在多线程情况下,顺序有可能是 1.1->2.1->2.2->2.3->1.2。也就是说,在生产者还没wait之前,消费者就已经notifyAll了,这样的话,生产者会一直等下去。
所以,要解决这个问题,必须在wait和notifyAll的时候,获得该对象的锁,以保证同步。
请看以下利用wait,notify实现的一个生产者、一个消费者和一个单位的缓存的简单模型:

public class QueueBuffer {
int n;
boolean valueSet = false;
synchronized int get() {
if (!valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
System.out.println("Got: " + n);
valueSet = false;
notify();
return n;
}
synchronized void put(int n) {
if (valueSet)
try {
wait();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}
this.n = n;
valueSet = true;
System.out.println("Put: " + n);
notify();
}
}


public class Producer implements Runnable {
private QueueBuffer q;
Producer(QueueBuffer q) {
this.q = q;
new Thread(this, "Producer").start();
}
public void run() {
int i = 0;
while (true) {
q.put(i++);
}
}
}


public class Consumer implements Runnable {
private QueueBuffer q;
Consumer(QueueBuffer q) {
this.q = q;
new Thread(this, "Consumer").start();
}
public void run() {
while (true) {
q.get();
}
}
}


public class Main {
public static void main(String[] args) {
QueueBuffer q = new QueueBuffer();
new Producer(q);
new Consumer(q);
System.out.println("Press Control-C to stop.");
}
}

所以,JVM通过在执行的时候抛出IllegalMonitorStateException的异常,来确保wait, notify时,获得了对象的锁,从而消除隐藏的Race Condition。
最后来看看一道题:写一个多线程程序,交替输出1,2,1,2,1,2......
利用wait, notify解决:

1 public class OutputThread implements Runnable {
2
3 private int num;
4 private Object lock;
5
6 public OutputThread(int num, Object lock) {
7 super();
8 this.num = num;
9 this.lock = lock;
10 }
11
12 public void run() {
13 try {
14 while(true){
15 synchronized(lock){
16 lock.notifyAll();
17 lock.wait();
18 System.out.println(num);
19 }
20 }
21 } catch (InterruptedException e) {
22 // TODO Auto-generated catch block
23 e.printStackTrace();
24 }
25
26 }
27
28 public static void main(String[] args){
29 final Object lock = new Object();
30
31 Thread thread1 = new Thread(new OutputThread(1,lock));
32 Thread thread2 = new Thread(new OutputThread(2, lock));
33
34 thread1.start();
35 thread2.start();
36 }
37
38 }

《Java Concurrency in Practice》里的第14章,对wait, notify有更加详细的介绍。
参考:
http://javarevisited.blogspot.hk/2011/05/wait-notify-and-notifyall-in-java.html
JAVA线程同步 (一)wait(), notify()和notifyAll()使用的更多相关文章
- java 线程之间通信以及notify与notifyAll区别。
jvm多个线程间的通信是通过 线程的锁.条件语句.以及wait().notify()/notifyAll组成. 下面来实现一个启用多个线程来循环的输出两个不同的语句. package com.app. ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll(转)
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- java 线程同步 原理 sleep和wait区别
java线程同步的原理java会为每个Object对象分配一个monitor, 当某个对象(实例)的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处 ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- java线程同步问题——由腾讯笔试题引发的风波
刚刚wm问我了一道线程的问题,因为自己一直是coder界里的渣渣.所以就须要恶补一下. 2016年4月2号题目例如以下. import java.util.logging.Handler; /** * ...
- JAVA - 线程同步和线程调度的相关方法
JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...
- Java线程同步_1
Java线程同步_1 synchronized 该同步机制的的核心是同步监视器,任何对象都可以作为同步监视器,代码执行结束,或者程序调用了同步监视器的wait方法会导致释放同步监视器 synchron ...
- Java线程同步之一--AQS
Java线程同步之一--AQS 线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序.同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务.每个服务员在同一时刻 ...
- java线程 同步临界区:thinking in java4 21.3.5
java线程 同步临界区:thinking in java4 21.3.5 thinking in java 4免费下载:http://download.csdn.net/detail/liangru ...
- Java线程同步的四种方式详解(建议收藏)
Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...
随机推荐
- TongWeb
TongWeb 编辑 本词条缺少名片图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! 作为国内领先的中间件开发商,是国内最早研究J2EE技术和开发应用服务器产品的厂商.应用服务器TongWe ...
- DOS环境进入及基本命令
DOS:磁盘操作系统(Disk Operating System) Window环境下如何进入DOS: 1. 以win10为例,按ctrl+R打开运行窗口,在输入框输入"CMD"并 ...
- Android 支付宝快捷支付集成及ALI64错误的有效解决
支付宝开放平台採用了RSA安全签名机制,开发人员能够通过支付宝公钥验证消息来源.同一时候可使用自己的私钥对信息进行加密. RSA算法及数字签名机制是支付宝开放平台与开发人员网关安全通信的基础.若开发人 ...
- c程序设计语言第一章3
字符数组是C语言中最常用的数组类型.下面我们通过编写一个程序,来说明字符数组以反操作字符数组的函数的用法.该程序读入一组文本行,并把最长的文水行打印出来.该算法的基本框架非常简单: while (还有 ...
- Apatch常用的commons工具包介绍
1.Commons BeanUtils http://jakarta.apache.org/commons/beanutils/index.html 说明:针对Bean的一个工具集.由于Bean往往是 ...
- ubuntu如何修改root密码
安装完Ubuntu后忽然意识到没有设置root密码,不知道密码自然就无法进入根用户下.到网上搜了一下,原来是这麽回事.Ubuntu的默认root密码是随机的,即每次开机都有一个新的root密码.我们可 ...
- 简单的glib测试(三)
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <locale.h&g ...
- 对JS闭包的理解
闭包,是JS里很重要的一个概念,也是相对来讲不太容易理解的一个东西,不过即使难理解,我们也要迎难而上啊,嘿嘿,网上有很多文章都在讲闭包,我在看JS设计模式的时候,书里也着重讲了闭包,但是书里官方的的确 ...
- SpringMVC实战(注解)
1.前言 前面几篇介绍了SpringMVC中的控制器以及视图之间的映射方式,这篇来解说一下SpringMVC中的注解,通过注解能够非常方便的訪问到控制器中的某个方法. 2.配置文件配置 2.1 注解驱 ...
- com.mongodb. org.mongodb.