【Java 线程的深入研究3】最简单实例说明wait、notify、notifyAll的使用方法
wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
- 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
- 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
- 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
其中wait方法有三个over load方法:
wait()
wait(long)
wait(long,int)
wait方法通过参数可以指定等待的时长。如果没有指定参数,默认一直等待直到被通知。
以下是一个演示代码,以最简洁的方式说明复杂的问题:
简要说明下:
NotifyThread是用来模拟3秒钟后通知其他等待状态的线程的线程类;
WaitThread是用来模拟等待的线程类;
等待的中间对象是flag,一个String对象;
main方法中同时启动一个Notify线程和三个wait线程;
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 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();
}
}
OK,如果你拿这段程序去运行下的话, 会发现根本运行不了,what happened?满屏的java.lang.IllegalMonitorStateException。
没错,这段程序有很多问题,我们一个个来看。
首先,这儿要非常注意的几个事实是
- 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
- 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
- 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
- JVM基于多线程,默认情况下不能保证运行时线程的时序性
基于以上几点事实,我们需要确保让线程拥有对象的控制权。
也就是说在waitThread中执行wait方法时,要保证waitThread对flag有控制权;
在notifyThread中执行notify方法时,要保证notifyThread对flag有控制权。
线程取得控制权的方法有三:
- 执行对象的某个同步实例方法。
- 执行对象对应类的同步静态方法。
- 执行对该对象加同步锁的同步块。
synchronized (flag) {
flag = "false";
flag.notify();
}
synchronized (flag) {
while (flag!="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!");
}
我们向前进了一步。
问题解决了吗?
好像运行还是报错java.lang.IllegalMonitorStateException。what happened?
这时的异常是由于在针对flag对象同步块中,更改了flag对象的状态所导致的。如下:
flag="false";
flag.notify();
对在同步块中对flag进行了赋值操作,使得flag引用的对象改变,这时候再调用notify方法时,因为没有控制权所以抛出异常。
我们可以改进一下,将flag改成一个JavaBean,然后更改它的属性不会影响到flag的引用。
我们这里改成数组来试试,也可以达到同样的效果:
rivate String flag[] = {"true"};
synchronized (flag) {
flag[0] = "false";
flag.notify();
}
synchronized (flag) {
while (flag[0]!="false") {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
这时候再运行,不再报异常,但是线程没有结束是吧,没错,还有线程堵塞,处于wait状态。
原因很简单,我们有三个wait线程,只有一个notify线程,notify线程运行notify方法的时候,是随机通知一个正在等待的线程,所以,现在应该还有两个线程在waiting。
我们只需要将NotifyThread线程类中的flag.notify()方法改成notifyAll()就可以了。notifyAll方法会通知所有正在等待对象控制权的线程。
最终完成版如下:
public class NotifyTest {
private String flag[] = { "true" };
class NotifyThread extends Thread {
public NotifyThread(String name) {
super(name);
}
public void run() {
try {
sleep(3000);
} 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() {
synchronized (flag) {
while (flag[0] != "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();
}
}
转自:http://longdick.iteye.com/blog/453615
【Java 线程的深入研究3】最简单实例说明wait、notify、notifyAll的使用方法的更多相关文章
- 【Java 线程的深入研究2】常用函数说明
①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) ②join():指等待t线程终止. 使用方式. join是Thread类的一个方法,启动线程后直接调用, ...
- Java线程和多线程(二)——对象中的wait,notify以及notifyAll方法
Java对象中的wait,notify以及notifyAll方法 在Java的Object类中包含了3个final的方法,这三个方法允许线程来交流资源是否被锁定.这三个方法就是wait(),notif ...
- JAVA线程锁-读写锁应用,简单的缓存系统
在JAVA1.5版本以后,JAVA API中提供了ReadWriteLock,此类是一个接口,在它的实现类中ReentrantReadWriteLock中有这样一段代码 class CachedDat ...
- 【Java 线程的深入研究1】Java 提供了三种创建线程的方法
Java 提供了三种创建线程的方法: 通过实现 Runnable 接口: 通过继承 Thread 类本身: 通过 Callable 和 Future 创建线程. 1.通过实现 Runnable 接口来 ...
- 【Java 线程的深入研究4】ThreadPoolExecutor运转机制详解
hreadPoolExecutor机制 一.概述 1.ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调 ...
- java从字符串中提取数字的简单实例
随便给你一个含有数字的字符串,比如: String s="eert343dfg56756dtry66fggg89dfgf"; 那我们如何把其中的数字提取出来呢?大致有以下几种方法, ...
- java构造器执行顺序一个有趣的简单实例
一 Animal为父类,构造器中调用public(default.protected) say方法,Dog继承了Animal,并重载了say方法.新建Dog对象,查看运行结果,若将Animal中say ...
- Java线程并发控制基础知识
微博上众神推荐今年4月刚刚出版的一本书,淘宝华黎撰写的<大型网站系统与Java中间件实践>,一线工程师的作品,实践出真知,果断要看. 前两章与<淘宝技术这十年>内容类似,基本是 ...
- java线程方面的知识
java中单继承,多实现的: 若为多继承,那么当多个父类中有重复的属性或者方法时,子类的调用结果会含糊不清,因此用了单继承. 为什么是多实现呢? 通过实现接口拓展了类的功能,若实现的多个接口中有重复的 ...
随机推荐
- 黑客编程教程(十三)多线程DOS程序
DOS基本原理相信大家都已经很熟悉了,DOS工具大家也用的很熟悉.在群里 经常有人说什么时候去DOS什么东西. 现在我们就自己编写一个DOS工具. #include <winsock2.h> ...
- C# 获取指定目录下所有文件信息
/// <summary> /// 返回指定目录下所有文件信息 /// </summary> /// <param name="strDirectory&quo ...
- #pragma GCC system_header用法
在看公司公共库的头文件中发现了:#pragma GCC system_header一行,以前没有见过这种用法,在网上查了一下,解释如下: 从#pragma GCC system_header直到文件结 ...
- JS转换HTML转义符,防止javascript注入攻击,亲测可用
function removeHtmlTab(tab) { return tab.replace(/<[^<>]+?>/g,'');//删除所有HTML标签 } functi ...
- 使用Konva创建进度条
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Markdown 11种基本语法【转】
[转自:http://www.cnblogs.com/hnrainll/p/3514637.html] 1. 标题设置(让字体变大,和word的标题意思一样)在Markdown当中设置标题,有两种方式 ...
- Log4php 使用心得
使用log4php 记录系统日志: 1.自动拦截php报出的错误,写日志 2.手动打印错误 set_error_handler('captureNormal',E_ERROR | E_PARSE); ...
- thymeleaf 的hello world
在研究一个模板引擎,选了这个thymeleaf .中间遇到很多的问题.现在在这里记录一下. 第一步:导入jar包.这里使用maven导入jar包 <!-- thymeleaf 核心包 begin ...
- SVN回滚至某个版本
今天新上传的版本出错了,需要回滚至之前的版本. 记录一下过程. 在项目文件夹下. 1.打开日志历史.右键单击,TortoiseSVN-->show log 2.要回滚至版本x.在版本x上右键单击 ...
- [镜像]镜像操作losetup
安装完之后查看一下版本 /usr/sbin/debootstrap –version转自:http://blog.csdn.net/cnyyx/article/details/27182833 1.挂 ...