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

来源:http://www.cnblogs.com/oaks/archive/2016/05/05/5462553.html
-Advertisement-
Play Games

Java線程通訊方法之wait()、nofity() 詳解 本文將探討以下問題: 1. synchronized 代碼塊使用 2. notify()與notifyAll()的區別 3. Java wait(),notify()如何使用 參考文章: "Java並行(2): Monitor" "Java ...


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 。當一個線程列印完畢之後自己就會掛起,必須等待另外一個線程列印並將
之喚醒,就實現了交替列印的效果。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • import java.util.ArrayList;import java.util.Scanner; /* * 題目描述: * 讀入數據string[ ],然後讀入一個短字元串。要求查找string[ ]中和短字元串的所有匹配,輸出行號、匹配字元串。 * 匹配時不區分大小寫,並且可以有一個用中括 ...
  • 全文純手打,不喜勿噴~ OOP封裝 //封裝一個類:用來管理普通人的特性和行為 //1、類的概念:人類在認識客觀世界時,發現某些事物具有共同特性,共同結構,共同行為。 // 為了方便,我們就將它們集合起來,並抽象出一個概念類管理它們,這個概念就是類。 //2、類的組成:標準類由:實例變數、構造器、設 ...
  • 一、遞歸函數 概念:遞歸演算法是一種直接或者間接的調用自身演算法的過程。在電腦編寫程式中,遞歸演算法對解決一大類問題是十分有效的。 特點: ①遞歸就是在過程或者函數里調用自身。 ②在使用遞歸策略時,必須有一個明確的遞歸條件,稱為遞歸出口。 ③遞歸演算法解題通常顯得很簡潔,但遞歸演算法解題的效率較低。所以一般 ...
  • 問題? Java7新增了關於文件屬性信息的一些新特性,通過java.nio.file.*包下麵的類可以實現設置或者讀取文件的元數據信息(比如最後修改時間,創建時間,文件大小,是否為目錄等等)。尤其是UserDefinedFileAttributeView,可以用來自定義文件的元數據信息。於是在自己的 ...
  • 1. 初識解釋器ghci 1.1 查看幫助: :? 1.2 修改提示符: :set prompt ghci>>> 1.3 加自己指定模塊: :module + Data.Ratio 2. 基本交互 2.1 基本算術運算 中綴表達式: 首碼表達式: 2.2 算術中的負數 -8其實並不是直接表示負數8, ...
  • 實現界面 涉及到四張表,type(商品類型表),type_spec(商品類型規格關聯表),attribute(商品屬性表),attribute_value(商品屬性值表) 新建基控制器BaseController.class.php,向上抽取出來的公用方法 BaseController.class. ...
  • [源碼下載] 速戰速決 (4) - PHP: 類基礎, 抽象類, 介面, trait 作者:webabcd介紹速戰速決 之 PHP 類基礎 抽象類 介面 trait 示例1、類的相關知識點 1(基礎)class/class1.php 2、類的相關知識點 2(抽象類,介面,trait)class/cl ...
  • 另:如何在eclipse中改類名,--右鍵類,Refactor >> Rename 即可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...