引言

在Java中,可以通过配合调用Object对象的wait,notify和notifyAll来实现线程间的通信。

在线程中调用wait方法,将阻塞带带其他线程的通知(其他线程调用notify或notifyAll)。

在线程中调用notify或notifyAll将通知其他线程从wait方法处返回。

Object是所有类的父类,它有5个方法组成了等待/通知机制的核心:notify(),notifyAll(),wait(),wait(long)和wait(long, int)。

在Java中,所有的类都从Object继承而来,因此所有的类都拥有这些共有方法。而且由于他们都被声明为final,因此在子类中不能覆写任何一个方法。

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

wait  

该方法用来将当前线程置于休眠状态,直到接到通知或被中断,在调用wait方法之前,线程必须要获得该对象级别锁,否则编译可以通过,但是运行会抛出异常IllegalMonitorStateException(它是RuntimeException的一个子类,不需要try-catch结构),即只能在同步方法或同步块中调用wait。进入wait方法后,当前线程阻塞,并立刻释放锁。在从wait返回前,线程与其他线程竞争重新获得锁。

(1)线程必须要获得该对象级别锁

package com.huawei.thread;

public class Test36 {

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

运行截图:

wait(long)/wait(long, int)

显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

notify

(1)该方法也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,如果调用notify时没有持有适当的锁,也会抛出IllegalMonitorStateException。

实例:调用lock对象的notify方法,但是获取的锁却是obj对象,所以抛出异常。也就是说,要在某个对象上执行wait、notify方法,先必须锁定该对象。

package com.huawei.thread;

public class Test36 {

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

运行截图:

(2)该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait状态的线程来发出通知,并使它等待获取该对象的对象锁。

package com.huawei.thread;

public class Test36 {

	public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread a = new Thread(new A(obj));
Thread b = new Thread(new B(obj));
a.setName("a");
b.setName("b");
a.start();
b.start();
Thread.sleep(2000);
synchronized (obj) {
obj.notify();
}
}
} class A implements Runnable {
private Object obj; public A(Object obj) {
this.obj = obj;
} @Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting...");
obj.wait();
System.out.println(Thread.currentThread().getName() + " is end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class B implements Runnable {
private Object obj; public B(Object obj) {
this.obj = obj;
} @Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting...");
obj.wait();
System.out.println(Thread.currentThread().getName() + " is end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}  

运行截图:(1)首先a和b分别进入wait阻塞(2)2秒之后main线程调用notify通知了一个线程,a被唤醒执行(3)由于notify只能唤醒一个线程,所以b就永久阻塞了

可以看出:当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。

(3)notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到notify所在线程退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才能可以获取该对象锁。

package com.huawei.thread;

import java.util.Date;

public class Test36 {

	public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread a = new Thread(new A(obj));
Thread b = new Thread(new B(obj));
a.setName("a");
b.setName("b");
a.start();
b.start();
Thread.sleep(2000);
synchronized (obj) {
obj.notifyAll();
System.out.println(new Date());
Thread.sleep(3000);
}
}
} class A implements Runnable {
private Object obj; public A(Object obj) {
this.obj = obj;
} @Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting...");
obj.wait();
System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class B implements Runnable {
private Object obj; public B(Object obj) {
this.obj = obj;
} @Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting...");
obj.wait();
System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}  

运行截图:可以看出主线程notifyAll之后会sleep(3000),然后才会释放锁,b线程也是在3秒之后才能获取到锁。

在实际编程中,我们应该尽量在线程调用notify/notifyAll后,立即退出临界区释放锁,即不要在notify/notifyAll后面再写一些耗时的代码。

另外:如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。

notifyAll

notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。 

wait/notify模式

通常,多线程之间需要协调工作:如果条件不满足,则等待;当条件满足时,等待该条件的线程将被唤醒,在Java中,这个模式的实现依赖wait/notify。

synchronized(obj) {
  while(!condition) {
   obj.wait();
  }
  obj.doSomething();
}  

当线程A获得obj锁后,发现条件condition不满足,无法继续下一步处理,于是线程A就wait;

另一个线程B中,如果B改改了某些条件,使得线程A的condition条件满足,就可以唤醒线程A:

synchronized(obj) {
  condition = true;
  obj.notifyAll();
} 

为什么条件测试使用while,而不是if?

实例:有两个线程从List中删除数据,而只有一个线程向List中添加数据。初始时,List为空,只有往List中添加了数据之后,才能删除List中的数据。添加数据的线程向List添加完数据后,调用notifyAll(),唤醒了两个删除线程,但是它只添加了一个数据,而现在有两个唤醒的删除线程,这时如果使用if会怎样?

结论:如果用 if 测试List中的数据的个数,则会出现IndexOutofBoundException。越界异常。原因是,List中只有一个数据,第一个删除线程把数据删除后,第二个线程再去执行删除操作时,删除失败,从而抛出 IndexOutofBoundException。

package com.huawei.thread;

import java.util.ArrayList;
import java.util.List; public class Test37 {
public static List<String> list = new ArrayList<String>(); public static void main(String[] args) throws InterruptedException {
Object lock = new Object(); SubtractThread sub1 = new SubtractThread(lock);
sub1.setName("sub1"); SubtractThread sub2 = new SubtractThread(lock);
sub2.setName("sub2"); sub1.start();
sub2.start(); Thread.sleep(2000);
AddThread adder = new AddThread(lock);
adder.setName("adder");
adder.start(); } } class AddThread extends Thread {
private Object lock; public AddThread(Object lock) {
this.lock = lock;
} @Override
public void run() {
synchronized (lock) {
Test37.list.add("test");
lock.notifyAll();
}
}
} class SubtractThread extends Thread {
private Object lock; public SubtractThread(Object lock) {
this.lock = lock;
} @Override
public void run() {
try {
synchronized (lock) {
if (Test37.list.isEmpty()) {//将这里的if改成while即可保证不出现越界异常
System.out.println("wait begin thread name: " + Thread.currentThread().getName());
lock.wait();
System.out.println("wait end thread name: " + Thread.currentThread().getName());
}
Test37.list.remove(0);
System.out.println("list size=" + Test37.list.size());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行截图:

另外:

(1)如果要把wait和notify放在一起使用的话,必须先调用notify方法,后调用wait。

(2)假设线程A执行wait,线程B执行notify:如果B先执行了notify然后结束,A执行wait阻塞,那此时A将无法被正常唤醒。(当然可以通过interrupt()方法以抛出异常的方式唤醒A)

wait与中断

请参考《多线程-interrupt(),isInterrupted(),interrupted()》

小结

(1)如果线程调用了对象的wait方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

(2)当有线程调用了对象的notifyAll方法(唤醒所有wait线程)或notify方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

(3)优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中

(4)唯有线程再次调用wait方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

参考资料

http://blog.csdn.net/ns_code/article/details/17225469

http://blog.csdn.net/oracle_microsoft/article/details/6863662

http://www.cnblogs.com/hapjin/p/5492645.html

http://www.cnblogs.com/pangyang/articles/5916349.html

多线程-wait/notify/notifyAll的更多相关文章

  1. Java多线程_wait/notify/notifyAll方法

    关于这三个方法,我们可以查询API得到下列解释: wait():导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法或者指定的事件用完 notify( ...

  2. Java多线程--wait(),notify(),notifyAll()的用法

    忙等待没有对运行等待线程的 CPU 进行有效的利用(而且忙等待消耗cpu过于恐怖,请慎用),除非平均等待时间非常短.否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号. Java ...

  3. Java多线程8:wait()和notify()/notifyAll()

    轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处 ...

  4. Java多线程的wait(),notify(),notifyAll()

    在多线程的情况下.因为多个线程与存储空间共享相同的过程,同时带来的便利.它也带来了访问冲突这个严重的问题. Java语言提供了一种特殊的机制来解决这类冲突,避免同一数据对象由多个线程在同一时间访问. ...

  5. Java多线程学习之wait、notify/notifyAll 详解

    1.wait().notify/notifyAll() 方法是Object的本地final方法,无法被重写. 2.wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关 ...

  6. Java多线程:wait(),notify(),notifyAll()

    1. wait(),notify(),notifyAll() 2. wait() 2.1. wait() 2.2. wait(long timeout) 2.3. wait(long timeout, ...

  7. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  8. java多线程14 :wait()和notify()/notifyAll()

    轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处 ...

  9. 多线程学习-基础(六)分析wait()-notify()-notifyAll()

    一.理解wait()-notify()-notifyAll()obj.wait()与obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,notify是针对已经获 ...

随机推荐

  1. jquery缩写,显示隐藏

    $(".a").css("display")=="none" ?$(".a").css("display&qu ...

  2. C/C++控制台输出时设置字体及背景颜色

    1.改变整个控制台的颜色用 system("color 0A"); 其中color后面的0是背景色代号,A是前景色代号.各颜色代码如下: 0=黑色 1=蓝色 2=绿色 3=湖蓝色  ...

  3. 万里长征第二步——django个人博客(第三步 —— 设置一些全局变量)

    可以将一些全局变量设置在settingg.py里 #网站的基本信息配置 SITE_NAME = 'John的个人博客' SITE_DESC = '专注学习Python开发,欢迎和大家交流' WEIBO ...

  4. C语言void关键字的深刻含义

    C语言void关键字的深刻含义 .概述 本文将对void关键字的深刻含义进行解说,并详述void及void指针类型的使用方法与技巧. .void的含义 void的字面意思是“无类型”,void *则为 ...

  5. jquery ajax 中不能给变量赋值的原因及解决办法

    我们在用JQuery的Ajax从后台提取数据后想把它赋值给全局变量,但是却怎么都赋不进,为什么呢? 原因其实很简单,我们用的Ajax是异步操作,也就是说在你赋值的时候数据还没提取出来,你当然赋不进去, ...

  6. C# 调用 Web Service 时出现 : 407 Proxy Authentication Required错误的解决办法

    // 记得 using System.Net; System.Net.WebProxy myProxy = new System.Net.WebProxy("localhost:9099&q ...

  7. 解决:mysql5.7 timestamp默认值0000-00-00 00:00:00 报错

    解决:mysql5.7 timestamp默认值0000-00-00 00:00:00 报错 学习了:https://www.cnblogs.com/cnhkzyy/p/9119339.html se ...

  8. 云计算之路-试用Azure-飞流直下三千尺:实测虚拟机磁盘IO

    Azure的Temporary Storage(临时存储)磁盘的IO速度曾经是个传说,只知道它很快,但不知道究竟有多快.而Azure中国的情况怎么样,我们来实测一下. 测试环境:Azure上海机房,1 ...

  9. ffmpeg代码解析

    void avdevice_register_all(void){    static int initialized;    if (initialized)        return;    i ...

  10. Git学习笔记四--远程仓库

    Git远程仓库 Git是分布式版本控制系统,同一个Git仓库,可以分布到不同的机器上. 怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本 ...