<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          wait 和 notify 有坑。。

          共 14895字,需瀏覽 30分鐘

           ·

          2021-08-13 20:16

          相關(guān)閱讀

          300本計算機編程的經(jīng)典書籍下載

          AI全套:Python3+TensorFlow打造人臉識別智能小程序

          最新人工智能資料-Google工程師親授 Tensorflow-入門到進階

          Java架構(gòu)全階段七期完整

          黑馬頭條項目 - Java Springboot2.0(視頻、資料、代碼和講義)14天完整版

          Spring核心編程思想

          作者:忘凈空

          鏈接:https://www.jianshu.com/p/91d95bb5a4bd

          也許我們只知道wait和notify是實現(xiàn)線程通信的,同時要使用synchronized包住,其實在開發(fā)中知道這個是遠遠不夠的。

          接下來看看兩個常見的問題。

          問題一:通知丟失

          創(chuàng)建2個線程,一個線程負責計算,一個線程負責獲取計算結(jié)果。

          public class Calculator extends Thread {
              int total;

              @Override
              public void run() {
                  synchronized (this){
                      for(int i = 0; i < 101; i++){
                          total += i;
                      }
                      this.notify();
                  }

              }
          }

          public class ReaderResult extends Thread {
              Calculator c;
              public ReaderResult(Calculator c) {
                  this.c = c;
              }

              @Override
              public void run() {
                  synchronized (c) {
                      try {
                          System.out.println(Thread.currentThread() + "等待計算結(jié)...");
                          c.wait();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println(Thread.currentThread() + "計算結(jié)果為:" + c.total);
                  }
              }


              public static void main(String[] args) {
                  Calculator calculator = new Calculator();
                  //先啟動獲取計算結(jié)果線程
                  new ReaderResult(calculator).start();
                  calculator.start();
                  
              }
          }

          我們會獲得預(yù)期的結(jié)果:

          Thread[Thread-1,5,main]等待計算結(jié)...
          Thread[Thread-1,5,main]計算結(jié)果為:5050

          但是我們修改為先啟動計算線程呢?

          calculator.start();
          new ReaderResult(calculator).start();

          這是獲取結(jié)算結(jié)果線程一直等待:

          Thread[Thread-1,5,main]等待計算結(jié)...

          問題分析

          打印出線程堆棧:

          "Thread-1" prio=5 tid=0x00007f983b87e000 nid=0x4d03 in Object.wait() [0x0000000118988000]
             java.lang.Thread.State: WAITING (on object monitor)
              at java.lang.Object.wait(Native Method)
              - waiting on <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)
              at java.lang.Object.wait(Object.java:503)
              at com.concurrent.waitnotify.ReaderResult.run(ReaderResult.java:18)
              - locked <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)

          可以看出ReaderResult在Calculator上等待。

          發(fā)生這個現(xiàn)象就是常說的通知丟失,在獲取通知前,通知提前到達,我們先計算結(jié)果,計算完后再通知。

          但是這個時候獲取結(jié)果沒有在等待通知,等到獲取結(jié)果的線程想獲取結(jié)果時,這個通知已經(jīng)通知過了,所以就發(fā)生丟失,那我們該如何避免?可以設(shè)置變量表示是否被通知過。

          修改代碼如下:

          public class Calculator extends Thread {
              int total;
              boolean isSignalled = false;

              @Override
              public void run() {
                  synchronized (this) {
                      isSignalled = true;//已經(jīng)通知過
                          for (int i = 0; i < 101; i++) {
                              total += i;
                          }
                          this.notify();
                      }
              }
          }

          public class ReaderResult extends Thread {

              Calculator c;

              public ReaderResult(Calculator c) {
                  this.c = c;
              }

              @Override
              public void run() {
                  synchronized (c) {
                      if (!c.isSignalled) {//判斷是否被通知過
                          try {
                              System.out.println(Thread.currentThread() + "等待計算結(jié)...");
                              c.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread() + "計算結(jié)果為:" + c.total);
                      }

                  }
              }

              public static void main(String[] args) {
                  Calculator calculator = new Calculator();
                  new ReaderResult(calculator).start();
                  calculator.start();
              }
          }

          問題二:假喚醒

          兩個線程去刪除數(shù)組的元素,當沒有元素的時候等待,另一個線程添加一個元素,添加完后通知刪除數(shù)據(jù)的線程。

          public class EarlyNotify{
              private List list;

              public EarlyNotify() {
                  list = Collections.synchronizedList(new LinkedList());
              }

              public String removeItem() throws InterruptedException {

                  synchronized ( list ) {
                      if ( list.isEmpty() ) {  //問題在這
                          list.wait();
                      }

                      //刪除元素
                      String item = (String) list.remove(0);
                      return item;
                  }
              }

              public void addItem(String item) {
                  synchronized ( list ) {
                      //添加元素
                      list.add(item);
                      //添加后,通知所有線程
                      list.notifyAll();
                  }
              }

              private static void print(String msg) {
                  String name = Thread.currentThread().getName();
                  System.out.println(name + ": " + msg);
              }

              public static void main(String[] args) {
                  final EarlyNotify en = new EarlyNotify();

                  Runnable runA = new Runnable() {
                      public void run() {
                          try {
                              String item = en.removeItem();

                          } catch ( InterruptedException ix ) {
                              print("interrupted!");
                          } catch ( Exception x ) {
                              print("threw an Exception!!!\n" + x);
                          }
                      }
                  };

                  Runnable runB = new Runnable() {
                      public void run() {
                          en.addItem("Hello!");
                      }
                  };

                  try {
                      //啟動第一個刪除元素的線程
                      Thread threadA1 = new Thread(runA, "threadA1");
                      threadA1.start();

                      Thread.sleep(500);

                      //啟動第二個刪除元素的線程
                      Thread threadA2 = new Thread(runA, "threadA2");
                      threadA2.start();

                      Thread.sleep(500);
                      //啟動增加元素的線程
                      Thread threadB = new Thread(runB, "threadB");
                      threadB.start();

                      Thread.sleep(1000); // wait 10 seconds

                      threadA1.interrupt();
                      threadA2.interrupt();
                  } catch ( InterruptedException x ) {}
              }
          }

          結(jié)果:

          threadA1: threw an Exception!!!
          java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

          這里發(fā)生了假喚醒,當添加完一個元素然后喚醒兩個線程去刪除,這個只有一個元素,所以會拋出數(shù)組越界,這時我們需要喚醒的時候在判斷一次是否還有元素。Java 最新技術(shù)教程和示例源碼參考:https://github.com/javastacks/javastack

          修改代碼:

          public String removeItem() throws InterruptedException {

                synchronized ( list ) {
                    while ( list.isEmpty() ) {  //問題在這
                        list.wait();
                    }

                    //刪除元素
                    String item = (String) list.remove(0);
                    return item;
                }
          }

          等待/通知的典型范式

          從上面的問題我們可歸納出等待/通知的典型范式。

          該范式分為兩部分,分別針對等待方(消費者)和通知方(生產(chǎn)者)。

          等待方遵循原則如下:

          1. 獲取對象的鎖
          2. 如果條件不滿足,那么調(diào)用對象的wait()方法,被通知后仍要檢查條件
          3. 條件滿足則執(zhí)行對應(yīng)的邏輯

          對應(yīng)偽代碼如下:

          synchronized(對象){
              while(條件不滿足){
                  對象.wait();
              }
              對應(yīng)的處理邏輯
          }

          通知方遵循原則如下:

          1. 獲得對象的鎖
          2. 改變條件
          3. 通知所以等待在對象上的線程

          對應(yīng)偽代碼如下:

          synchronized(對象){
              改變條件
              對象.notifyAll();
          }



          看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人


          往期資源:


          Flutter 移動應(yīng)用開發(fā)實戰(zhàn) 視頻(開發(fā)你自己的抖音APP)
          Java面試進階訓練營 第2季(分布式篇)
          Java高級 - 分布式系統(tǒng)開發(fā)技術(shù)視頻


          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲天堂视频在线免费观看 | 内射毛片在线免费看 | 国产婷婷激情综合 | 久久影院三级片 | 男女日皮免费视频 |