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

  • 相关阅读:
    Shader-另类实现
    Unity3d- 资源
    我不快乐
    陷入了一个NGUI自适应的一个坑
    ngui 自适应
    NGUI的数据绑定
    我已经很努力,但是依旧写给自己
    那些奇葩的叫法
    C#修改类模板
    开启Unity3D之旅
  • 原文地址:https://www.cnblogs.com/expiator/p/9292009.html
Copyright © 2011-2022 走看看