線程通信

来源:https://www.cnblogs.com/coding-diary/archive/2019/07/10/11166416.html
-Advertisement-
Play Games

線程通信的方式 要想實現線程之間的協同, 如: 線程先後執行順序, 獲取某個線程的執行結果等, 涉及線程之間的相互通信, 分為下麵四類 文件共用 網路共用 變數共用 JDK提供的線程協調API 細分為: suspend/resume, wait/notify, park/unpark 文件共用 變數 ...


線程通信的方式

要想實現線程之間的協同, 如: 線程先後執行順序, 獲取某個線程的執行結果等, 涉及線程之間的相互通信, 分為下麵四類

  • 文件共用
  • 網路共用
  • 變數共用
  • JDK提供的線程協調API 細分為: suspend/resume, wait/notify, park/unpark

文件共用

img

變數共用

img

線程協作 - JDK API

典型場景: 生產者 - 消費者模型 (線程阻塞, 線程喚醒)
示例: 線程1區買包子 , 沒有包子, 則不執行。 線程2生產包子, 通知線程1繼續執行
img

API - 被棄用的suspend和resume
調用suspend掛起目標線程, 通過resume可以恢複線程執行, 對調用順序有要求,也要開發者自己註意鎖的釋放。這個被棄用的API, 容易死鎖,也容易導致永久掛起。

代碼示例:

  /** 正常的suspend/resume */
  public void suspendResumeTest() throws Exception {
    // 啟動線程
    Thread consumerThread =
        new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                System.out.println("1、進入等待");
                Thread.currentThread().suspend();
              }
              System.out.println("2、買到包子,回家");
            });
    consumerThread.start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    consumerThread.resume();
    System.out.println("3、通知消費者");
  }
  /** 死鎖的suspend/resume。 suspend並不會像wait一樣釋放鎖,故此容易寫出死鎖代碼 */
  public void suspendResumeDeadLockTest() throws Exception {
    // 啟動線程
    Thread consumerThread =
        new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                System.out.println("1、進入等待");
                // 當前線程拿到鎖,然後掛起
                synchronized (this) {
                  Thread.currentThread().suspend();
                }
              }
              System.out.println("2、買到包子,回家");
            });
    consumerThread.start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    // 爭取到鎖以後,再恢復consumerThread
    synchronized (this) {
      consumerThread.resume();
    }
    System.out.println("3、通知消費者");
  }
  /** 先執行resume再執行suspend導致程式永久掛起的suspend/resume */
  public void suspendResumeDeadLockTest2() throws Exception {
    // 啟動線程
    Thread consumerThread =
        new Thread(
            () -> {
              if (baozidian == null) {
                System.out.println("1、沒包子,進入等待");
                try { // 為這個線程加上一點延時
                  Thread.sleep(5000L);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
                // 這裡的掛起執行在resume後面
                Thread.currentThread().suspend();
              }
              System.out.println("2、買到包子,回家");
            });
    consumerThread.start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    consumerThread.resume();
    System.out.println("3、通知消費者");
    consumerThread.join();
  }

wait/notify機制

這些方法只能由同一對象鎖的線程持有者調用,也就是寫在同步代碼塊裡面, 否則會拋出IllegalMonitorStateException異常。
wait方法導致當前線程等待, 加入該對象的等待集合中, 並且放棄當前持有的對象鎖
notify/notifyAll喚醒一個/所有正在等待這個對象鎖的線程
註意: 雖然wait會自動解鎖, 但對順序有要求, 如果在notify被調用之後, 才開始wait方法的調用, 線程會永遠處於WAINTING狀態

代碼示例:

/** 正常的wait/notify */
  public void waitNotifyTest() throws Exception {
    // 啟動線程
    new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                synchronized (this) {
                  try {
                    System.out.println("1、進入等待");
                    this.wait();
                  } catch (InterruptedException e) {
                    e.printStackTrace();
                  }
                }
              }
              System.out.println("2、買到包子,回家");
            })
        .start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    synchronized (this) {
      this.notifyAll();
      System.out.println("3、通知消費者");
    }
  }
/** 會導致程式永久等待的wait/notify */
  public void waitNotifyDeadLockTest() throws Exception {
    // 啟動線程
    new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                try {
                  Thread.sleep(5000L);
                } catch (InterruptedException e1) {
                  e1.printStackTrace();
                }
                synchronized (this) {
                  try {
                    System.out.println("1、進入等待");
                    this.wait();
                  } catch (InterruptedException e) {
                    e.printStackTrace();
                  }
                }
              }
              System.out.println("2、買到包子,回家");
            })
        .start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    synchronized (this) {
      this.notifyAll();
      System.out.println("3、通知消費者");
    }
  }

park/unpark機制

線程調用park則等待“許可”, unpark方法為指定線程提供“許可”。 不要求park和unpark方法的調用順序。 多次調用unpark後再調用park, 線程會直接運行, 但不會疊加, 也就是說, 連續多次調用park方法, 第一次會拿到“許可”直接運行, 後續調用會進入等待。
註意: park/unpark 對調用順序沒有要求, 但是並不會釋放鎖

代碼示例:

/** 正常的park/unpark */
  public void parkUnparkTest() throws Exception {
    // 啟動線程
    Thread consumerThread =
        new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                System.out.println("1、進入等待");
                LockSupport.park();
              }
              System.out.println("2、買到包子,回家");
            });
    consumerThread.start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    LockSupport.unpark(consumerThread);
    System.out.println("3、通知消費者");
  }
 /** 死鎖的park/unpark */
  public void parkUnparkDeadLockTest() throws Exception {
    // 啟動線程
    Thread consumerThread =
        new Thread(
            () -> {
              if (baozidian == null) { // 如果沒包子,則進入等待
                System.out.println("1、進入等待");
                // 當前線程拿到鎖,然後掛起
                synchronized (this) {
                  LockSupport.park();
                }
              }
              System.out.println("2、買到包子,回家");
            });
    consumerThread.start();
    // 3秒之後,生產一個包子
    Thread.sleep(3000L);
    baozidian = new Object();
    // 爭取到鎖以後,再恢復consumerThread
    synchronized (this) {
      LockSupport.unpark(consumerThread);
    }
    System.out.println("3、通知消費者");
  }

偽喚醒

之前代碼中用if語句來判斷是否進入等待是錯誤的
官方建議應該在迴圈中檢查條件,原因是處於等待狀態的線程可能會收到錯誤警報和偽喚醒, 如果不在迴圈中檢查等待條件, 程式就會在沒有滿足結束條件的情況下退出

偽喚醒 :指線程並非因為notify, notifyAll, unpark等API調用而喚醒, 是更底層的原因導致的。

img


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

-Advertisement-
Play Games
更多相關文章
  • 本人剛學先上鏈接(別人寫的挺好的)後期同步補上😄!!! 網站鏈接:https://blog.csdn.net/baozhiqiangjava/article/details/81178694 GitHub:https://github.com/JsAaron/jQuery ...
  • SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 前面我們在聊服務網關Zuul的時候提到了Gateway,那麼Z ...
  • "JavaScript 設計模式基礎(一)" "小菜鳥的個人博客" 原型模式 在以類為中心的面向對象編程語言中,類和對象的關係就像鑄模和鑄件的關係,對象總是從類中創建。而原型編程中,類不是必須的,對象未必從類中創建而來,可以拷貝另一個對象而變成新對象 從設計模式角度講,原型模式是用於創建對象的一種模 ...
  • 相比較傳統的工廠模式IFactory/Concrete Factory會反覆引用並編譯代碼 但是作為開發人員,我們更希望的是少修改代碼,儘量從配置著手也就是設計模式的根本原則之一:開放封閉原則。如果我要增加新的產品,那麼修改就比較大了,對於業務來講還是可以接受的。但是如果可以做到不修改代碼是最好的。 ...
  • 架構雜談《二》 服務化到微服務 1、微服務的產生 隨著互聯網企業的不斷發展,海量用戶發起的大規模、高併發請求是企業不得不面對的,上一篇 架構雜談《一》雜談的SOA服務化系統能夠分解任務,讓每個服務更簡單、職責單一、更易於擴展。但無論是Web Service 還是ESB,都有時代遺留下的問題。 Web ...
  • 一些討論 1. [Python中使用配置文件的最佳實踐][1] 2. [Python中使用配置文件的最好方法][2] 3. [Python符號常量][3] 4. [多種配置文件方案對比][4] [1]: %20%E5%8F%82%E8%A7%81SO%E7%9A%84%E8%AE%A8%E8%AE% ...
  • 1.代碼生成器: [正反雙向](單表、主表、明細表、樹形表,快速開發利器)freemaker模版技術 ,0個代碼不用寫,生成完整的一個模塊,帶頁面、建表sql腳本、處理類、service等完整模塊2.多數據源:(支持同時連接無數個資料庫,可以不同的模塊連接不同數的據庫)支持N個數據源3.阿裡資料庫連 ...
  • 本文為本次系列文章的第一篇,接下來,小編預計用一周的時間,帶大家重新解讀二十三中設計模式。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...