wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态

public final native void notify();

public final native void notifyAll();

public final native void wait(long l)
throws InterruptedException; public final void wait(long l, int i)
throws InterruptedException
{
if(l < 0L)
throw new IllegalArgumentException("timeout value is negative");
if(i < 0 || i > 999999)
throw new IllegalArgumentException("nanosecond timeout value out of range");
if(i > 0)
l++;
wait(l);
} public final void wait()
throws InterruptedException
{
wait(0L);
}

这三个方法最终调用的都是jvm级的final native方法。随着jvm运行平台的不同可能有些许差异。

  • 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
  • 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
  • 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

其中wait方法有三个over load方法:

wait()

wait(long)

wait(long,int)

wait方法通过参数可以指定等待的时长。如果没有指定参数,默认一直等待直到被通知。

以下是一个演示代码,以最简洁的方式说明复杂的问题:

简要说明下:

NotifyThread是用来模拟3秒钟后通知其他等待状态的线程的线程类;

WaitThread是用来模拟等待的线程类;

等待的中间对象是flag,一个String对象;

main方法中同时启动一个Notify线程和三个wait线程;

package com.dxz.synchronizeddemo;

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() {
System.out.println(getName() + " flag:" + flag);
while (!flag.equals("false")) {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
System.out.println("wait time :" + waitTime);
}
System.out.println(getName() + " end waiting!"); }
} public static void main(String[] args) throws InterruptedException {
System.out.println("Main Thread Run!");
NotifyTest test = new NotifyTest();
NotifyThread notifyThread = test.new NotifyThread("notify01");
WaitThread waitThread01 = test.new WaitThread("waiter01");
WaitThread waitThread02 = test.new WaitThread("waiter02");
WaitThread waitThread03 = test.new WaitThread("waiter03");
notifyThread.start();
waitThread01.start();
waitThread02.start();
waitThread03.start();
}
}

结果:

Main Thread Run!
Exception in thread "waiter03" waiter03 flag:true
waiter02 flag:true
waiter03 begin waiting!
waiter01 flag:true
waiter02 begin waiting!
waiter01 begin waiting!
Exception in thread "waiter02" Exception in thread "waiter01" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
Exception in thread "notify01" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.dxz.synchronizeddemo.NotifyTest$NotifyThread.run(NotifyTest.java:19)

在wait和notify的地方都报错java.lang.IllegalMonitorStateException,前面也讲过,wait和notify方法一定要在synchronized里面,更具体点说有:

  1. 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
  2. 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
  3. 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
  4. JVM基于多线程,默认情况下不能保证运行时线程的时序性

基于以上几点事实,我们需要确保让线程拥有对象的控制权。

也就是说在waitThread中执行wait方法时,要保证waitThread对flag有控制权;

在notifyThread中执行notify方法时,要保证notifyThread对flag有控制权。

线程取得控制权的方法有三:见《Synchronized之一:基本使用

  1. 同步的实例方法(锁用的是其实例对象本身。所有的非静态同步方法执行需要顺序执行,即不能并行执行。)
  2. 同步的静态方法(锁用的是其类对象本身。所有的静态同步方法执行需要顺序执行,即不能并行执行。)
  3. 实例方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象)
  4. 静态方法中的同步块(锁是自己指定的,但不能是引用性对象及null对象)
我们用第三种方法来做说明:
将以上notify和wait方法包在同步块中

//nofity放入synchronized中
synchronized (flag) {
flag = "false";
flag.notify();
}
//wait放入synchronized中
synchronized (flag) {
while (!flag.equals("false")) {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
System.out.println("wait time :" + waitTime);
}
}

再运行的结果:

Main Thread Run!
waiter02 flag:true
waiter01 flag:true
waiter03 flag:true
waiter02 begin waiting!
waiter03 begin waiting!
waiter01 begin waiting!
Exception in thread "notify01" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.dxz.synchronizeddemo.NotifyTest$NotifyThread.run(NotifyTest.java:20)

在flag.notify();的地方还是会报错java.lang.IllegalMonitorStateException。这时的异常是由于在针对flag对象同步块中,更改了flag对象的状态所导致的。如下:

flag="false";  //改变的对象的内容
flag.notify();

对在同步块中对flag进行了赋值操作,使得flag引用的对象改变,这时候再调用notify方法时,因为没有控制权所以抛出异常。

我们可以改进一下,将flag改成一个JavaBean,然后更改它的属性不会影响到flag的引用。

我们这里改成数组来试试,也可以达到同样的效果:

这时候再运行,不再报异常,但是线程没有结束是吧,没错,还有线程堵塞,处于wait状态。

原因很简单,我们有三个wait线程,只有一个notify线程,notify线程运行notify方法的时候,是随机通知一个正在等待的线程,所以,现在应该还有两个线程在waiting。

我们只需要将NotifyThread线程类中的flag.notify()方法改成notifyAll()就可以了。notifyAll方法会通知所有正在等待对象控制权的线程。

最终完成版如下:

package com.dxz.synchronizeddemo;

public class NotifyTest2 {
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();
}
synchronized (flag) {
flag[0] = "false";
flag.notify();
}
}
}; class WaitThread extends Thread {
public WaitThread(String name) {
super(name);
} public void run() {
synchronized (flag) {
System.out.println(getName() + " flag:" + flag);
while (!flag[0].equals("false")) {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
System.out.println("wait time :" + waitTime);
}
System.out.println(getName() + " end waiting!");
} }
} public static void main(String[] args) throws InterruptedException {
System.out.println("Main Thread Run!");
NotifyTest2 test = new NotifyTest2();
NotifyThread notifyThread = test.new NotifyThread("notify01");
WaitThread waitThread01 = test.new WaitThread("waiter01");
WaitThread waitThread02 = test.new WaitThread("waiter02");
WaitThread waitThread03 = test.new WaitThread("waiter03");
notifyThread.start();
waitThread01.start();
waitThread02.start();
waitThread03.start();
}
}

notify()和notifyAll()的本质区别

notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:

notifyAll使所有原来在该对象上等待被notify的所有线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify则文明得多,它只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

下面是一个很好的例子:

package com.dxz.synchronizeddemo;

public class NotifyTest3 {
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();
} synchronized (flag) {
flag[0] = "false";
flag.notifyAll();
}
}
}; class WaitThread extends Thread {
public WaitThread(String name) {
super(name);
} public void run() {
System.out.println(getName() + " flag:" + flag);
synchronized (flag) {
System.out.println(getName() + " flag:" + flag);
while (!flag[0].equals("false")) {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
System.out.println("wait time :" + waitTime);
}
System.out.println(getName() + " end waiting!");
}
System.out.println(getName() + " end waiting!"); }
} public static void main(String[] args) throws InterruptedException {
System.out.println("Main Thread Run!");
NotifyTest3 test = new NotifyTest3();
NotifyThread notifyThread = test.new NotifyThread("notify01");
WaitThread waitThread01 = test.new WaitThread("waiter01");
WaitThread waitThread02 = test.new WaitThread("waiter02");
WaitThread waitThread03 = test.new WaitThread("waiter03");
notifyThread.start();
waitThread01.start();
waitThread02.start();
waitThread03.start();
}
}

结果:

Main Thread Run!
waiter01 flag:[Ljava.lang.String;@584aceca
waiter01 flag:[Ljava.lang.String;@584aceca
waiter02 flag:[Ljava.lang.String;@584aceca
waiter01 begin waiting!
waiter02 flag:[Ljava.lang.String;@584aceca
waiter02 begin waiting!
waiter03 flag:[Ljava.lang.String;@584aceca
waiter03 flag:[Ljava.lang.String;@584aceca
waiter03 begin waiting!
wait time :3000
waiter03 end waiting!
waiter03 end waiting!
wait time :3001
waiter02 end waiting!
waiter02 end waiting!
wait time :3001
waiter01 end waiting!
waiter01 end waiting!
 

并发编程之二:wait、notify、notifyAll的使用方法的更多相关文章

  1. 并发编程之wait()、notify()

    前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...

  2. python并发编程之multiprocessing进程(二)

    python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...

  3. Java并发编程之CAS二源码追根溯源

    Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...

  4. 并发编程之:CountDownLatch

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 先问大家一个问题,在主线程中创建多个线程,在这多个线程被启动之后,主线程需要等子线程执行完之后才能接着执行自己的代码,应该怎么实现呢? Thread. ...

  5. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  6. python并发编程之Queue线程、进程、协程通信(五)

    单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...

  7. python并发编程之gevent协程(四)

    协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...

  8. python并发编程之asyncio协程(三)

    协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...

  9. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

随机推荐

  1. java.util.concurrent Class ThreadPoolExecutor

    http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html

  2. Qt事件和信号的区别 .

    仔细来看,事件与信号其实并无多大差别,从我们对其需求上来说,都只要能注册事件或信号响应函数,在事件或信号产生时能够被通知到即可.但有一项区别在于,事件处理函数的返回值是有意义的,我们要根据这个返回值来 ...

  3. gcviewer待整理

    官网:https://github.com/chewiebug/GCViewer G1 Gabage Collector -XX:+UnlockExperimentalVMOptions -XX:+U ...

  4. [机器学习之13]降维技术——主成分分析PCA

    始终贯彻数据分析的一个大问题就是对数据和结果的展示,我们都知道在低维度下数据处理比较方便,因而数据进行简化成为了一个重要的技术.对数据进行简化的原因: 1.使得数据集更易用使用.2.降低很多算法的计算 ...

  5. PHP读某一个目录下所有文件和文件夹

    废话少说了 直接上代码 <?php function read_dir($dir) { if (!is_dir($dir)) { echo 'not a dir '; return; } if ...

  6. Uncaught TypeError: Illegal constructor(…)

    使用jquery  $(Document);会报这个错误.因为 Document(大写) 是函数. document(小写) 才是文档.

  7. wikioi 2235 机票打折 【考查浮点数四舍五入的技巧】

    /*======================================================================== 2235 机票打折 题目描述 Descriptio ...

  8. python compile

    compile(source, filename, mode[, flags[, dont_inherit]]) 参数source:字符串或者AST(Abstract Syntax Trees)对象. ...

  9. 在.net中悄悄执行dos命令,并获取执行的结果(转)

    一.怎样使dos命令悄悄执行,而不弹出控制台窗口? 1.需要执行带“/C”参数的“cmd.exe”命令,它表示执行完命令后立即退出控制台.2.设置startInfo.UseShellExecute = ...

  10. jquery的$.extend和$.fn.extend作用及区别

    jQuery为开发插件提拱了两个方法,分别是: jQuery.fn.extend(); jQuery.extend(); (1)类级别 类级别你可以理解为拓展jquery类,最明显的例子是$.ajax ...