Java線程通訊方法之wait()、nofity() 詳解


Java線程通訊方法之wait()、nofity() 詳解

本文將探討以下問題:

  1. synchronized 代碼塊使用
  2. notify()與notifyAll()的區別
  3. Java wait(),notify()如何使用

參考文章:
Java並行(2): Monitor
Java並發編程:線程間協作的兩種方式:wait、notify、notifyAll和Condition
Java的wait(), notify()和notifyAll()使用心得
原創文章,歡迎轉載!

synchronized 代碼塊使用

 我們知道當多個線程同時訪問一個線程安全方法時我們可以使用synchronized關鍵字來給方法加鎖。當某個線程需
要訪問方法時,會先獲取訪問該方法的鎖,當訪問完畢再釋放鎖。當多個線程在等待調用隊列中,操作系統根據一
定的調度算法,取出下一個線程來執行方法,完成方法的並行到串行的執行過程。每個對象都擁有一個Monitor,我
們可以將Monitor理解為對象的鎖。每個線程訪問任意對象時必須要先獲取該對象的Monitor 才能訪問。當synchro-
nized修飾一個對象時,它控制多線程訪問該對象的方法正是通過對象的Monitor實現。請看下面計數代碼:

public class SynchronizedImpl implements Runnable{
    public static  MyInteger num = new MyInteger() ;
    public void run() {
        // 鎖定 num 引用的對象
        synchronized (num){
            // 對num 的成員變量value自增,步進為1
            num.setValue(num.getValue()+1);
            System.out.println(Thread.currentThread().getName()+":"+SynchronizedImpl.num.getValue());
        }
    }
    public static void main(String[] args) {
        for(int i =0 ; i < 1000 ;i++){
                Thread t =  new Thread(new SynchronizedImpl());
                t.start();
        }
    }
}
class MyInteger{
    int value =0 ;
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}

 上述代碼 num 所引用的對象(ps: 事實上num 並不是一個對象,只是棧中的一個引用,記錄的是它所引用的對象的地址,下
文為了敘述方便把num 稱為對象) ,同時被多個線程訪問。(ps:事實上不會是1000個線程同時訪問,同時訪問一個對象的
線程數小於等於cpu的核心數)。
從打印的結果來看,雖然線程並不是順序執行,但是保證每個線程都訪問一次num對象,並且對num對象的 value 屬性 +1 。

notify() 和notifyAll()

  顧名思義notify()是喚醒一個正在等待的線程,notifyAll()是喚醒所有正在等待的線程。這樣的解釋 並不會讓我們很好理解
二者之間的區別。

notify()
  notify是通知操作系統喚醒一個正在等待的獲取對象鎖的線程,當有多個等待線程時候, 操作系統會根據一定的調度算法調
度一個線程,當正在占有對象鎖的線程釋放鎖的時候操作系統調度的這個線程就會執行。而而剩下的那些沒有被notify的線程
就不會獲取執行機會。

notifyAll()
  當有多個等待線程時,所有的等待線程都會被喚醒,他們會根據操作系統的調度順序依次執行。下面的代碼說明二者的區別:

public class NofityTest implements Runnable {
    private Object lock ;
    private int num ;
    public NofityTest(Object lock, int i) {
        this.lock = lock ;
        num = i ;
    }

    @Override
    public void run() {
            synchronized(lock){
                try {
                    lock.wait();
                    System.out.println("--- "+num);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
    }

    public static void main(String[] args) {
        // 利用obj 的wait 方法實
        final  Object lock = new Object() ;
        new Thread(new NofityTest(lock,1)).start();
        new Thread(new NofityTest(lock,2)).start();
        try {
            // 等待另外兩個線程進入wait狀態
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock){
        // 請將 lock.notify() 改成lock.notifyAll() 再執行觀察二者區別 !!
            lock.notify();
        }
    }
}

wait()和notify()

 對象的wait()是讓當前線程釋放該對象Monitor鎖並且進入訪問該對象的等待隊列,當前線程會進入掛起狀態,等待操作系統喚起(notify)
掛起的線程重新獲取對該對象的訪問鎖才能進入運行狀態。因為自身已經掛起,所以已經掛起的線程無法喚醒自己,必須通過別的線程
告訴操作系統,再由操作系統喚醒。Monitor是不能被並發訪問的(否則Monitor狀態會出錯,操作系統根據錯誤的狀態調度導致系統錯亂),
而wait和nofity 正是改變Monitor的狀態(請參考 PV操作) 所以使用wait、notify方法時,必須對對象使用synchronized加鎖,只有線程獲
取對象的Monitor鎖之后才能進行wait、notify操作否則將拋出IllegalMonitorStateException異常。我們來看一段代碼:

    public class PrintAB implements Runnable{
      private Object lock ;
      private char ch ;
      public PrintAB(Object lock ,char ch){
          this.lock = lock ;
          this.ch = ch ;
      }
      @Override
      public void run() {
          while(true){
              synchronized (lock){
                  try {
                      /**
                       * 第一次執行並不會喚醒任何線程,
                       * 第二次以及以后就會喚醒另外一個線程獲取Monitor鎖,因為只有一個線程掛起
                       * 而notify() 就是喚醒一個線程
                       */
                      lock.notify();
                      System.out.println(Thread.currentThread().getName()+":"+ch);
                      /**
                       * synchronized 代碼塊執行完畢之后才會交出Monitor鎖,別的線程才有執行機會
                       * wait 執行過之后當前線程就掛起了,然后釋放鎖,接着已經被操作系統notify
                       * 的線程獲取Monitor 開始執行
                       */
                      lock.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
  
      public static void main(String[] args) {
          // 我們最好將 lock 聲明為final ,防止重新賦值后導致synchronized鎖定對象發生改變。
          final Object lock = new Object();
          new Thread(new PrintAB(lock,'A')).start();
          new Thread(new PrintAB(lock,'B')).start();
      }
  }

上述代碼實現了交替打印 字符A 和字符B 。當一個線程打印完畢之后自己就會掛起,必須等待另外一個線程打印並將
之喚醒,就實現了交替打印的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM