热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java多线程之wait_JAVA多线程之wait/notify

本文主要学习JAVA多线程中的wait()方法与notify()notifyAll()方法的用法。①wait()与notifynotifyAll方法必须在同步代码块中使用ÿ

本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法。

①wait() 与 notify/notifyAll 方法必须在同步代码块中使用,否则会抛出如下异常:

58da607b3d4fb13697fa300d5cee8e08.png

②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()后面再写一些耗时的代码。示例如下:

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就有机会获得另一个线程释放的锁,并从等待的地方起(第9行)起开始执行。

接下来是两个线程类,线程类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。

示例如下: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();

}

}



推荐阅读
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了在Python张量流中使用make_merged_spec()方法合并设备规格对象的方法和语法,以及参数和返回值的说明,并提供了一个示例代码。 ... [详细]
author-avatar
寒月繁华叶落尽
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有