JAVA多线程之wait/notify
本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法。
①wait() 与 notify/notifyAll 方法必须在同步代码块中使用
②wait() 与 notify/notifyAll() 的执行过程
③中断 调用wait()方法进入等待队列的 线程
④notify 通知的顺序不能错
⑤多线程中测试某个条件的变化用 if 还是用 while?
①wait() 与 notify/notifyAll 方法必须在同步代码块中使用
wait() 与 notify/notifyAll() 是Object类的方法,在执行两个方法时,要先获得锁。那么怎么获得锁呢?
在这篇:JAVA多线程之Synchronized关键字--对象锁的特点文章中介绍了使用synchronized关键字获得锁。因此,wait() 与 notify/notifyAll() 经常与synchronized搭配使用,即在synchronized修饰的同步代码块或方法里面调用wait() 与 notify/notifyAll()方法。
②wait() 与 notify/notifyAll() 的执行过程
由于 wait() 与 notify/notifyAll() 是放在同步代码块中的,因此线程在执行它们时,肯定是进入了临界区中的,即该线程肯定是获得了锁的。
当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行,直到执行完退出对象锁锁住的区域(synchronized修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行完临界区中代码后,再释放。故,在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出临界区。即不要在notify/notifyAll()后面再写一些耗时的代码。
示例如下:(摘自《JAVA多线程核心技术》)

1 public class Service {
2
3 public void testMethod(Object lock) {
4 try {
5 synchronized (lock) {
6 System.out.println("begin wait() ThreadName="
7 + Thread.currentThread().getName());
8 lock.wait();
9 System.out.println(" end wait() ThreadName="
10 + Thread.currentThread().getName());
11 }
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15 }
16
17 public void synNotifyMethod(Object lock) {
18 try {
19 synchronized (lock) {
20 System.out.println("begin notify() ThreadName="
21 + Thread.currentThread().getName() + " time="
22 + System.currentTimeMillis());
23 lock.notify();
24 Thread.sleep(5000);
25 System.out.println(" end notify() ThreadName="
26 + Thread.currentThread().getName() + " time="
27 + System.currentTimeMillis());
28 }
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 }
33 }

在第3行的testMethod()中调用 wait(),在第17行的synNotifyMethod()中调用notify()
从上面的代码可以看出,wait() 与 notify/notifyAll()都是放在同步代码块中才能够执行的。如果在执行wait() 与 notify/notifyAll() 之前没有获得相应的对象锁,就会抛出:java.lang.IllegalMonitorStateException异常。
在第8行,当ThreadA线程执行lock.wait();这条语句时,释放获得的对象锁lock,并放弃CPU,进入等待队列。
当另一个线程执行第23行lock.notify();,会唤醒ThreadA,但是此时它并不立即释放锁,接下来它睡眠了5秒钟(sleep()是不释放锁的,事实上sleep()也可以不在同步代码块中调用),直到第28行,退出synchronized修饰的临界区时,才会把锁释放。这时,ThreadA就有机会获得另一个线程释放的锁,并从等待的地方起(第24行)起开始执行。
接下来是两个线程类,线程类ThreadA调用testMethod()方法执行lock.wait();时被挂起,另一个线程类synNotifyMethodThread调用synNotifyMethod()负责唤醒挂起的线程。代码如下:

1 public class ThreadA extends Thread {
2 private Object lock;
3
4 public ThreadA(Object lock) {
5 super();
6 this.lock = lock;
7 }
8
9 @Override
10 public void run() {
11 Service service = new Service();
12 service.testMethod(lock);
13 }
14 }
15
16 public class synNotifyMethodThread extends Thread {
17 private Object lock;
18
19 public synNotifyMethodThread(Object lock) {
20 super();
21 this.lock = lock;
22 }
23
24 @Override
25 public void run() {
26 Service service = new Service();
27 service.synNotifyMethod(lock);
28 }
29 }

再接下来是测试类:

1 public class Test {
2
3 public static void main(String[] args) throws InterruptedException {
4
5 Object lock = new Object();
6
7 ThreadA a = new ThreadA(lock);
8 a.start();
9
10 NotifyThread notifyThread = new NotifyThread(lock);
11 notifyThread.start();
12
13 synNotifyMethodThread c = new synNotifyMethodThread(lock);
14 c.start();
15 }
16 }

③中断 调用wait()方法进入等待队列的 线程
示例代码如下:

1 public class Service {
2
3 public void testMethod(Object lock) {
4 try {
5 synchronized (lock) {
6 System.out.println("begin wait()");
7 lock.wait();
8 System.out.println(" end wait()");
9 }
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 System.out.println("出现异常");
13 }
14 }
15 }
16
17 public class ThreadA extends Thread {
18
19 private Object lock;
20
21 public ThreadA(Object lock) {
22 super();
23 this.lock = lock;
24 }
25
26 @Override
27 public void run() {
28 Service service = new Service();
29 service.testMethod(lock);
30 }
31 }

注意,在第23行wait()方法是Object类的对象lock调用的。而下面的interrupt()方法是ThreadA类的对象调用的。在ThreadA里面,将Object的对象作为参数传给了testMethod()方法,ThreadA的run()方法去调用testMethod(),从而wait()使ThreadA的线程暂停了(暂停当前执行wait()的线程)。从这里可以看出一个区别:
Object类中与线程有关的方法:
1)notify/notifyAll
2)wait()/wait(long)
java.lang.Thread中与之相关的方法:
1)interrupt()
2)sleep()/sleep(long)
3)join()/suspend()/resume()....
测试类代码如下:

1 public class Test {
2
3 public static void main(String[] args) {
4
5 try {
6 Object lock = new Object();
7
8 ThreadA a = new ThreadA(lock);
9 a.start();
10
11 Thread.sleep(5000);
12
13 a.interrupt();
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 }
18 }

当执行第13行的interrupt()时,处于wait中的线程“立即”被唤醒(一般是立即响应中断请求),并抛出异常。此时,线程也就结束了。
④notify 通知的顺序不能错
假设在线程A中执行wait(),在线程B中执行notify()。但如果线程B先执行了notify()然后结束了,线程A才去执行wait(),那此时,线程A将无法被正常唤醒了(还可以通过③中提到的interrupt()方法以抛出异常的方式唤醒^~^)。
这篇文章: JAVA多线程之线程间的通信方式中的第③点提到了notify通知顺序出错会导致 调用wait()进入等待队列的线程再也无法被唤醒了。
⑤多线程中测试某个条件的变化用 if 还是用 while?
以前一直不明白 当在线程的run()方法中需要测试某个条件时,为什么用while,而不用if???直到看到了这个简单的例子,终于明白了。。。。
这个例子是这样的:
有两个线程从List中删除数据,而只有一个线程向List中添加数据。初始时,List为空,只有往List中添加了数据之后,才能删除List中的数据。添加数据的线程向List添加完数据后,调用notifyAll(),唤醒了两个删除线程,但是它只添加了一个数据,而现在有两个唤醒的删除线程,这时怎么办??
如果用 if 测试List中的数据的个数,则会出现IndexOutofBoundException,越界异常。原因是,List中只有一个数据,第一个删除线程把数据删除后,第二个线程再去执行删除操作时,删除失败,从而抛出 IndexOutofBoundException。
但是如果用while 测试List中数据的个数,则不会出现越界异常!!!神奇。
当wait等待的条件发生变化时,会造成程序的逻辑混乱---即,List中没有数据了,再还是有线程去执行删除数据的操作。因此,需要用while循环来判断条件的变化,而不是用if。
示例如下:摘自《JAVA多线程编程核心技术》
Add类,负责添加数据:

public class Add { private String lock;
public Add(String lock) {
super();
this.lock = lock;
} public void add() {
synchronized (lock) {
ValueObject.list.add("anyString");
lock.notifyAll();
}
}
} public class ThreadAdd extends Thread { private Add p; public ThreadAdd(Add p) {
super();
this.p = p;
} @Override
public void run() {
p.add();
}
}

Subtract类,负责删除数据----先要进行条件判断,然后执行wait(),这意味着:wait等待的条件可能发生变化!!!

public class Subtract { private String lock; public Subtract(String lock) {
super();
this.lock = lock;
} public void subtract() {
try {
synchronized (lock) {
if(ValueObject.list.size() == 0) {//将这里的if改成while即可保证不出现越界异常!!!!
System.out.println("wait begin ThreadName="
+ Thread.currentThread().getName());
lock.wait();
System.out.println("wait end ThreadName="
+ Thread.currentThread().getName());
}
ValueObject.list.remove(0);
System.out.println("list size=" + ValueObject.list.size());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public class ThreadSubtract extends Thread { private Subtract r; public ThreadSubtract(Subtract r) {
super();
this.r = r;
} @Override
public void run() {
r.subtract();
}
}

封装的List队列:
public class ValueObject { public static List list = new ArrayList(); }
测试类:

public class Run { public static void main(String[] args) throws InterruptedException { String lock = new String(""); Add add = new Add(lock);
Subtract subtract = new Subtract(lock); ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
subtract1Thread.setName("subtract1Thread");
subtract1Thread.start(); ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
subtract2Thread.setName("subtract2Thread");
subtract2Thread.start(); Thread.sleep(1000); ThreadAdd addThread = new ThreadAdd(add);
addThread.setName("addThread");
addThread.start(); }
}
JAVA多线程之wait/notify的更多相关文章
- java 多线程之wait(),notify,notifyAll(),yield()
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都 ...
- Java多线程之wait(),notify(),notifyAll()
在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候 ...
- JAVA多线程之volatile 与 synchronized 的比较
一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空 ...
- java多线程之wait和notify协作,生产者和消费者
这篇直接贴代码了 package cn.javaBase.study_thread1; class Source { public static int num = 0; //假设这是馒头的数量 } ...
- java多线程之yield,join,wait,sleep的区别
Java多线程之yield,join,wait,sleep的区别 Java多线程中,经常会遇到yield,join,wait和sleep方法.容易混淆他们的功能及作用.自己仔细研究了下,他们主要的区别 ...
- Java多线程之ConcurrentSkipListMap深入分析(转)
Java多线程之ConcurrentSkipListMap深入分析 一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下, ...
- Java多线程之Runnable与Thread
Java多线程之Thread与Runnable 一.Thread VS Runnable 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口:Thread类和 ...
- JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止
JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止 背景 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境 ...
- Java——多线程之Lock锁
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
随机推荐
- 微信 python 接口 -- itchat 文档
itchat 一. 安装 $ pip install itchat 特殊的字典使用方式 通过打印 itchat 的用户以及注册消息的参数, 可以发现这些值都是字典. 但实际上 itchat 精心构造了 ...
- 连接SQL SERVER数据库实例出错
问题背景: 刚买的新电脑,昨天让公司IT部装了win10专业版系统,想体验一下.装完后一切正常,晚上带回家装自己需要的软件和系统更新,结果今天来公司发现数据库实例连不上了,拿到IT部,他们也没有找到原 ...
- java高并发锁的3种实现
初级技巧 - 乐观锁 乐观锁适合这样的场景:读不会冲突,写会冲突.同时读的频率远大于写. 以下面的代码为例,悲观锁的实现: Java代码 public Object get(Object key) ...
- PHP按值合并数组
/** * PHP按值合并数组 * */ function my_array_merge(&$array1, &$array2) { $result = Array(); foreac ...
- 10.application对象
1.application对象实现了用户数据的共享,可存放全局变量 2.application开始于服务器的启动,终止于服务器的关闭. 3.在用户的前后连接或不同用户之间的连接中,可以对applica ...
- 你猜这个题输出啥?-- java基础概念
最近在看java编程思想,大部分内容都觉得没啥意思,但是突然看到一个基本概念(似乎都忘了),于是写了测试题,我想这辈子也不会忘这个概念了. 题目如下: public class Suber exten ...
- 关于SpringMVC中text/plain的编码导致的乱码问题解决方法
有老铁的项目出现个问题,就是用SpringMVC给前台返回一句话,是String类型的,然后前台接收到是乱码. 然后以为是简单的response的编码问题,就在方法体中开始给response设置编码, ...
- git常用基本命令
一定要以管理员的身份打开,否则有些命令不能用,比如ssh -T git@github.com(查看配置ssh是否成功)@初始化git git config --global user.name ruo ...
- MyBatis源码解析【4】反射和动态代理
通过之前的介绍,我们了解了几个组件的生命周期. 它也是我们重要装备之一. 今天我们需要搞一件更加强的装备,叫做反射和动态代理. 如果没有这件装备的话,显然后面的源码boss是打不动的. 顺便说一下,下 ...
- Jenkins 配置邮件通知
jenkins 是一个开源的自动化服务器.通过Jenkins,可以通过自动化加速软件开发过程.Jenkins管理和控制各种开发的生命周期过程,包括构建,文档,测试,包,阶段,部署,静态分析等等.您可以 ...