java併發基礎--等待通知模式

線程間通信

public class SynchronizedTest {

    public static void main(String[] args) {

        synchronized (SynchronizedTest.class){

        }

        m();
    }


    public synchronized static void m(){

    }
}

在編譯後的同一目錄
javap -v SynchronizedTest

Last modified 2015-12-2; size 587 bytes
  MD5 checksum faa56efff9ff6a17c0e345ae6503e899
  Compiled from "SynchronizedTest.java"
public class concurrent.SynchronizedTest
  SourceFile: "SynchronizedTest.java"
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#23         //  java/lang/Object."<init>":()V
   #2 = Class              #24            //  concurrent/SynchronizedTest
   #3 = Methodref          #2.#25         //  concurrent/SynchronizedTest.m:()V
   #4 = Class              #26            //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lconcurrent/SynchronizedTest;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               StackMapTable
  #17 = Class              #15            //  "[Ljava/lang/String;"
  #18 = Class              #26            //  java/lang/Object
  #19 = Class              #27            //  java/lang/Throwable
  #20 = Utf8               m
  #21 = Utf8               SourceFile
  #22 = Utf8               SynchronizedTest.java
  #23 = NameAndType        #5:#6          //  "<init>":()V
  #24 = Utf8               concurrent/SynchronizedTest
  #25 = NameAndType        #20:#6         //  m:()V
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Throwable
{
  public concurrent.SynchronizedTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lconcurrent/SynchronizedTest;

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc_w         #2                  // class concurrent/SynchronizedTe
st
         3: dup
         4: astore_1
         5: monitorenter   監視器進入獲取鎖
         6: aload_1
         7: monitorexit     監視器退出 釋放鎖
         8: goto          16 
        11: astore_2
        12: aload_1
        13: monitorexit
        14: aload_2
        15: athrow
        16: invokestatic  #3                  // Method m:()V
        19: return
      Exception table:
         from    to  target type
             6     8    11   any
            11    14    11   any
      LineNumberTable:
        line 10: 0
        line 12: 6
        line 14: 16
        line 15: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      20     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 11
          locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
           frame_type = 250 /* chop */
          offset_delta = 4


  public static synchronized void m();
  //方法修飾符
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 20: 0
}

對於同步快的實現使用了monitorenter 和 monitorexit指令 同步方法則是依靠方法修飾符上的ACC_SYNCHRONIZED 來完成 無論採用哪種方式 其本質都是對一個對象的監視器的獲取 這個過程是排他的
也就是同一時刻只能有一個線程能獲取到sync關鍵字所保護的監視器.

等待/通知機制

等待 通知機制的相關方法是任意java對象都具備的,因爲這些方法定義在java.lang.Object上

方法名稱 描述說明
notify() 通知一個在對象上等待的線程 使其從wait()方法返回 而返回的前提是該線程獲取到了對象的鎖
notifyAll() 通知所有等待在該對象上的線程
wait() 調用該方法的線程進入Waiting狀態 只有等待另外線程的通知或中斷纔會返回 需要注意 調用wait 方法後 會釋放對象的鎖
wait(long) 超時等待一段時間 這裏的參數時間是毫秒 也就是等待長達n毫秒 如果沒有通知則超時返回
wait(long,int) 對於超時時間更細粒度的控制 可以達到納秒

等待/通知機制 是指一個線程a調用了對象O的wait()方法進入等待狀態 而另一個線程b調用了對象o的notify()或者NotifyAll()方法 線程a收到通知後從對象o的wait方法中返回 執行後續的操作 上述倆個線程通過對象o來完成交互 而對象上的wait和notify的關係就如同開關信號一樣 用來完成等待方和通知方之間的交互

    static Object lock = new Object();

    static volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        new Thread(new Wait(),"wait-thread").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(new Notify(),"notify-thread").start();
    }



    static class Wait implements Runnable {

        @Override
        public void run() {
            //獲取鎖 擁有鎖的Monitor
            synchronized (lock){
                //當條件不滿足時 繼續wait 同時釋放了lock的鎖
                while (flag){
                    System.out.println("flag = true need wait @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("flag = false get here @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }


    static class Notify implements  Runnable {


        @Override
        public void run() {
            //枷鎖 擁有lock的monitor
            synchronized (lock){
                //獲取lock的鎖 然後進行通知 通知時不會釋放lock的鎖 直到當前線程釋放了 lock後 WaitThread 才從wait方法中返回
                lock.notify();
                System.out.println("hold lock @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                try {
                    TimeUnit.SECONDS.sleep(5);
                    flag = false;
                    System.out.println("hold lock @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

知識點:

1)使用wait() notify()或者nofityAll()時需要先對調用對象加鎖
2) 調用wait()方法後 線程由running變爲waiting 並將當前線程放置到對象的等待隊列
3)notify()或notifyAll()方法刁永紅 等待線程依舊不會從wait()方法返回 需要調用notify()或者notifyAll()的線程釋放鎖之後 等待線程纔有機會從wait()返回
4) notify()方法將等待隊列中的一個等待線程從等待隊列中移動到同步隊列中 而notifyAll()是將等待隊列中的所有線程全部移動到同步隊列 被移動的線程狀態由waiting變爲blocked
5)從wait()返回的前提是獲得了調用對象的鎖

等待/通知模式的經典範式

等待方:
1)獲取對象的鎖
2) 如果條件不滿足 那麼調用對象的wait方法 被通知後仍要檢查條件、
3) 條件滿足則執行對應的邏輯

僞碼:

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

通知方:

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

僞碼:

synchronized(對象){
 改變條件
 對象.notifyAll();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章