zoukankan      html  css  js  c++  java
  • java多线程通信方式之一:wait/notify

    java多线程之间的通信方式有多种:

    1.wait(),notify(),notifyAll()方法;2.join()方法;3.通过volatile共享内存的方式进行线程通信的;4.interrupt()方法中断线程; 5.管道通信。

    本文主要学习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()不在同步代码块中使用,那么会报错:IllegalMonitorStateException。

    ②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();
    
        }
    }
    复制代码

    参考:《JAVA多线程核心技术》

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

  • 相关阅读:
    Sqlserver 实际开发中表变量的用法
    Python Day 20 面向对象 (面向对象的组合用法,面向对象的三大特性
    Python Day 19 面向对象(初识面向对象)
    Python Day 18 常用模块(模块和包)
    Python Day 17 常用模块(常用模块一 时间模块,random模块,os模块,sys模块,序列化模块)
    Python Day 15 函数(递归函数、二分查找算法)
    Python Day 14 函数(内置函数,匿名函数(lambda表达式))
    Python Day 13 函数(迭代器,生成器,列表推导式,生成器表达式)
    Python Day 11 + Python Day 12 函数(函数名的应用,闭包,装饰器)
    Python Day 10 函数(名称空间,作用域,作用域链,加载顺序等; 函数的嵌套 global,nonlocal)
  • 原文地址:https://www.cnblogs.com/expiator/p/9292009.html
Copyright © 2011-2022 走看看