• Java多线程-Object.wait()


      sleep()和yield()方法,不会释放锁,而wait()方法会释放当前线程的锁,所以,wait()方法必须是在同步代码块中调用。

    • 应用场景

      多个线程合作执行某项任务的时候,有线程A和线程B,它们使用同一对象锁,同一时间它们只有其中之一可以运行,另外一个线程处于等待状态。如下事件图表所示

    •   线程A和线程B使用同一个把锁
    •   线程A工作时,线程B等待,线程B工作时,线程A等待
    •       在4点,线程A调用notify()唤醒线程B,并调用wait()方法,释放锁,任务挂起,这时线程B获取锁继续执行
    •       在11点, 线程B调用notify()唤醒线程A, 并调用wait()方法,释放锁,任务挂起,这时线程A获取锁继续执行

      使用wait()将当前任务挂起,让出CPU资源,比起使用while循环等待符合条件的时机,显然大大节省了CPU的开销。

    • wait()使用方式

      wait方法,必须写在同步代码块内,因为底层会使用当前对象的锁。Object.wait()方法的注释中,建议用户在while循环中使用wait,有以下几点原因:

      1.    处于等待中的线程A和线程B被线程C的notifyAll同时唤醒,而线程B还不满足继续执行的条件,下次循环再次挂起;
      2.      因为处于waiting状态中的线程,有可能被虚假唤醒(spurious wakeup),这种唤醒不是通过notify或者interrupt等常规方式,因此,需要继续恢复到wait状态,所以写在while循环中,尽管虚假唤醒发生的概率非           常低。
    1  synchronized (obj) {
    2         while (<condition does not hold>)
    3             obj.wait();
    4  // Perform action appropriate to condition
    5 } 

      唤醒wait的线程的方法是notify和notifyAll,notify, 两者的区别是,notifyAll唤醒所有等待当前对象锁的线程,notify随机唤醒一个等待当前对象锁的线程。一般而言,使用notifyAll,在wait和while循环配合来决定哪个线程继续执行。

      下面是一个例子,出自《thinking in java》,该程序是给一辆汽车反复打蜡wax、抛光buff,开启两个线程,一个打蜡,一个抛光,顺序是先打蜡再抛光,每次工作过程是:线程A打蜡,线程B等待抛光,线程A打蜡完成后唤醒线程B,线程B开始抛光,线程A等待线程B抛光,线程B抛光完成后唤醒线程A开始打蜡。。。。如此循环5秒钟,由shutdownNow中断所有线程.

     1 class Car {
     2     private boolean waxOn = false;
     3     public synchronized void waxed() {
     4         waxOn = true;   // Read to buff
     5         notifyAll();
     6     }
     7 
     8     public synchronized void buffed() {
     9         waxOn = false;  // Read for another coat of wax
    10         notifyAll();
    11     }
    12 
    13     public synchronized void waitForWaxing() throws InterruptedException {
    14         while (!waxOn) {
    15             wait();
    16         }
    17     }
    18 
    19     public synchronized void waitForBuffing() throws InterruptedException {
    20         while (waxOn) {
    21             wait();
    22         }
    23     }
    24 }
    25 
    26 class WaxOn implements Runnable {
    27     private Car car;
    28 
    29     public WaxOn(Car car) {
    30         this.car = car;
    31     }
    32 
    33     @Override
    34     public void run() {
    35         try {
    36             while (!Thread.interrupted()) {
    37                 System.out.println("Wax On! ");
    38                 TimeUnit.MILLISECONDS.sleep(200);
    39                 car.waxed();
    40                 car.waitForBuffing();   // buffing完了才能下一次wax
    41             }
    42         } catch (InterruptedException e) {
    43             System.out.println("Exiting via interrupt");
    44         }
    45         System.out.println("Ending Wax On task!");
    46     }
    47 }
    48 
    49 class WaxOff implements Runnable {
    50     private Car car;
    51 
    52     public WaxOff(Car car) {
    53         this.car = car;
    54     }
    55 
    56     @Override
    57     public void run() {
    58         try {
    59             while (!Thread.interrupted()) {
    60                 car.waitForWaxing();    // wax完后才能buff
    61                 System.out.println("Wax Off! ");
    62                 TimeUnit.MICROSECONDS.sleep(200);
    63                 car.buffed();
    64             }
    65         } catch (InterruptedException e) {
    66             System.out.println("Exiting via interrupt");
    67         }
    68         System.out.println("Ending Wax Off task");
    69     }
    70 }
    71 
    72 
    73 public class WaxOMatic {
    74     public static void main(String[] args) throws InterruptedException {
    75         Car car = new Car();
    76         ExecutorService exec = Executors.newCachedThreadPool();
    77         exec.execute(new WaxOn(car));
    78         exec.execute(new WaxOff(car));
    79         TimeUnit.SECONDS.sleep(5);
    80         exec.shutdownNow();
    81     }
    82 }
    •  防止死锁发生

      如下这种情况,如果线程A运行到了point1, 此时CPU调动切换到线程B,线程B进入上面的代码块,执行了notify,而此时线程A还未进入同步代码块,这时候就错过一次notify信号了,这时候进入同步代码块,就可能发生死锁永远无法被唤醒了。  

    synchronized (monitor) {
         // action
         monitor.notify();
    }
    
    
    while (<condition>) {
         // point1
         synchronized (monitor) {
             monitor.wait();
         }
    }

      如果要防止死锁,应该将while写在同步代码块之内

    synchronized (monitor) {
           // action
           monitor.notify();
    }
    
    synchronized (monitor) {
         while (<condition>) {
            monitor.wait();
         }
    }

      

  • 相关阅读:
    菜单范式
    PIC18F26K20
    单片机中串口通信模型
    STM8S103之GPIO
    STM8S103之ADC
    二叉树最近公共祖先
    全排列
    整数翻转
    完全二叉树节点个数
    二叉树的深度
  • 原文地址:https://www.cnblogs.com/yxlaisj/p/12202398.html
Copyright © 2020-2023  润新知