本文主要学习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. 1 public class Service {
  2. 2
  3. 3 public void testMethod(Object lock) {
  4. 4 try {
  5. 5 synchronized (lock) {
  6. 6 System.out.println("begin wait() ThreadName="
  7. 7 + Thread.currentThread().getName());
  8. 8 lock.wait();
  9. 9 System.out.println(" end wait() ThreadName="
  10. 10 + Thread.currentThread().getName());
  11. 11 }
  12. 12 } catch (InterruptedException e) {
  13. 13 e.printStackTrace();
  14. 14 }
  15. 15 }
  16. 16
  17. 17 public void synNotifyMethod(Object lock) {
  18. 18 try {
  19. 19 synchronized (lock) {
  20. 20 System.out.println("begin notify() ThreadName="
  21. 21 + Thread.currentThread().getName() + " time="
  22. 22 + System.currentTimeMillis());
  23. 23 lock.notify();
  24. 24 Thread.sleep(5000);
  25. 25 System.out.println(" end notify() ThreadName="
  26. 26 + Thread.currentThread().getName() + " time="
  27. 27 + System.currentTimeMillis());
  28. 28 }
  29. 29 } catch (InterruptedException e) {
  30. 30 e.printStackTrace();
  31. 31 }
  32. 32 }
  33. 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. 1 public class ThreadA extends Thread {
  2. 2 private Object lock;
  3. 3
  4. 4 public ThreadA(Object lock) {
  5. 5 super();
  6. 6 this.lock = lock;
  7. 7 }
  8. 8
  9. 9 @Override
  10. 10 public void run() {
  11. 11 Service service = new Service();
  12. 12 service.testMethod(lock);
  13. 13 }
  14. 14 }
  15. 15
  16. 16 public class synNotifyMethodThread extends Thread {
  17. 17 private Object lock;
  18. 18
  19. 19 public synNotifyMethodThread(Object lock) {
  20. 20 super();
  21. 21 this.lock = lock;
  22. 22 }
  23. 23
  24. 24 @Override
  25. 25 public void run() {
  26. 26 Service service = new Service();
  27. 27 service.synNotifyMethod(lock);
  28. 28 }
  29. 29 }

再接下来是测试类:

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

③中断 调用wait()方法进入等待队列的 线程

示例代码如下:

  1. 1 public class Service {
  2. 2
  3. 3 public void testMethod(Object lock) {
  4. 4 try {
  5. 5 synchronized (lock) {
  6. 6 System.out.println("begin wait()");
  7. 7 lock.wait();
  8. 8 System.out.println(" end wait()");
  9. 9 }
  10. 10 } catch (InterruptedException e) {
  11. 11 e.printStackTrace();
  12. 12 System.out.println("出现异常");
  13. 13 }
  14. 14 }
  15. 15 }
  16. 16
  17. 17 public class ThreadA extends Thread {
  18. 18
  19. 19 private Object lock;
  20. 20
  21. 21 public ThreadA(Object lock) {
  22. 22 super();
  23. 23 this.lock = lock;
  24. 24 }
  25. 25
  26. 26 @Override
  27. 27 public void run() {
  28. 28 Service service = new Service();
  29. 29 service.testMethod(lock);
  30. 30 }
  31. 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. 1 public class Test {
  2. 2
  3. 3 public static void main(String[] args) {
  4. 4
  5. 5 try {
  6. 6 Object lock = new Object();
  7. 7
  8. 8 ThreadA a = new ThreadA(lock);
  9. 9 a.start();
  10. 10
  11. 11 Thread.sleep(5000);
  12. 12
  13. 13 a.interrupt();
  14. 14 } catch (InterruptedException e) {
  15. 15 e.printStackTrace();
  16. 16 }
  17. 17 }
  18. 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类,负责添加数据:

  1. public class Add {
  2.  
  3. private String lock;
  4. public Add(String lock) {
  5. super();
  6. this.lock = lock;
  7. }
  8.  
  9. public void add() {
  10. synchronized (lock) {
  11. ValueObject.list.add("anyString");
  12. lock.notifyAll();
  13. }
  14. }
  15. }
  16.  
  17. public class ThreadAdd extends Thread {
  18.  
  19.     private Add p;
  20.  
  21.     public ThreadAdd(Add p) {
            super();
            this.p = p;
        }
  22.  
  23.     @Override
        public void run() {
            p.add();
        }
    }

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

  1. public class Subtract {
  2.  
  3. private String lock;
  4.  
  5. public Subtract(String lock) {
  6. super();
  7. this.lock = lock;
  8. }
  9.  
  10. public void subtract() {
  11. try {
  12. synchronized (lock) {
  13. if(ValueObject.list.size() == 0) {//将这里的if改成while即可保证不出现越界异常!!!!
  14. System.out.println("wait begin ThreadName="
  15. + Thread.currentThread().getName());
  16. lock.wait();
  17. System.out.println("wait end ThreadName="
  18. + Thread.currentThread().getName());
  19. }
  20. ValueObject.list.remove(0);
  21. System.out.println("list size=" + ValueObject.list.size());
  22. }
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28.  
  29. public class ThreadSubtract extends Thread {
  30.  
  31.     private Subtract r;
  32.  
  33.     public ThreadSubtract(Subtract r) {
            super();
            this.r = r;
        }
  34.  
  35.     @Override
        public void run() {
            r.subtract();
        }
    }

封装的List队列:

  1. public class ValueObject {
  2.  
  3. public static List list = new ArrayList();
  4.  
  5. }

测试类:

  1. public class Run {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4.  
  5. String lock = new String("");
  6.  
  7. Add add = new Add(lock);
  8. Subtract subtract = new Subtract(lock);
  9.  
  10. ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
  11. subtract1Thread.setName("subtract1Thread");
  12. subtract1Thread.start();
  13.  
  14. ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
  15. subtract2Thread.setName("subtract2Thread");
  16. subtract2Thread.start();
  17.  
  18. Thread.sleep(1000);
  19.  
  20. ThreadAdd addThread = new ThreadAdd(add);
  21. addThread.setName("addThread");
  22. addThread.start();
  23.  
  24. }
  25. }

JAVA多线程之wait/notify的更多相关文章

  1. java 多线程之wait(),notify,notifyAll(),yield()

    wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都 ...

  2. Java多线程之wait(),notify(),notifyAll()

    在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候 ...

  3. JAVA多线程之volatile 与 synchronized 的比较

    一,volatile关键字的可见性 要想理解volatile关键字,得先了解下JAVA的内存模型,Java内存模型的抽象示意图如下: 从图中可以看出: ①每个线程都有一个自己的本地内存空间--线程栈空 ...

  4. java多线程之wait和notify协作,生产者和消费者

    这篇直接贴代码了 package cn.javaBase.study_thread1; class Source { public static int num = 0; //假设这是馒头的数量 } ...

  5. java多线程之yield,join,wait,sleep的区别

    Java多线程之yield,join,wait,sleep的区别 Java多线程中,经常会遇到yield,join,wait和sleep方法.容易混淆他们的功能及作用.自己仔细研究了下,他们主要的区别 ...

  6. Java多线程之ConcurrentSkipListMap深入分析(转)

    Java多线程之ConcurrentSkipListMap深入分析   一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下, ...

  7. Java多线程之Runnable与Thread

    Java多线程之Thread与Runnable 一.Thread VS Runnable 在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口:Thread类和 ...

  8. JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

    JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止 背景 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境 ...

  9. Java——多线程之Lock锁

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

随机推荐

  1. Java IO学习笔记五

    管道流 管道流的主要作用是可以进行两个线程间的通讯,分为管道输出流(PipedOutputStream).管道输入流(PipedInputStream),如果想要进行管道输出,则必须要把输出流连在输入 ...

  2. node.js零基础详细教程(7.5):mongo可视化工具webstorm插件、nodejs自动重启模块Node Supervisor(修改nodejs后不用再手动命令行启动服务了)

    第七章 建议学习时间4小时  课程共10章 学习方式:详细阅读,并手动实现相关代码 学习目标:此教程将教会大家 安装Node.搭建服务器.express.mysql.mongodb.编写后台业务逻辑. ...

  3. WPF的一些感悟

    第一天在博客园写东西,只写一些自己对WPF稚嫩的理解和感悟. 1.Code Snippet代码简写工具 可以创建自己的代码模板管理器——>>>工具菜单,代码片段管理器 考出现有的+更 ...

  4. Normalize.css介绍,作用,使用方法

    介绍 Normalize.css 是一个很小的CSS文件(V5.0.0版本大小8KB),但它在默认的HTML元素样式上提供了跨浏览器的高度一致性.相比于传统的CSS reset,Normalize.c ...

  5. win7老是弹出“Windows Media PIayer网络共享服务配置应用程序 已停止工作”

    应是优化软件的时候把服务禁止启动了. 我的电脑 > 管理 > 服务和应用程序 > 服务 Windows Media Player Network Sharing Service 启动 ...

  6. MyBatis-sql映射文件

    Sql映射文件 MyBatis真正的力量是在映射语句中.这里是奇迹发生的地方.对于所有的力量,SQL映射的XML文件是相当的简单.当然如果你将它们和对等功能的JDBC代码来比较,你会发现映射文件节省了 ...

  7. Java分形

    目前笔者接触过的分形主要有一下几种: 1.类似Clifford的分形.这种分形的特点是:分形的初始坐标为(0,0),通过初始坐标经过大量的迭代,得到一系列的点,根据得到的点来绘制分形曲线.这类分形的参 ...

  8. 一次C++调试记录

         之前开发用Linux C比较多,C++中的STL 容器基本没有接触过.最近在学习C++,平时用到c++ 17中的部分新特性,下面就简单分享下自己C++的学习流程. 一.环境搭建      本 ...

  9. HDOJ 2209 贪心

    翻纸牌游戏 Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  10. React周末特训班

    Document #react819_content a { text-decoration: none; color: #ef7b2e; border-bottom: 1px solid #ef7b ...