線程通信和8鎖問題

来源:https://www.cnblogs.com/hexiayuliang666/archive/2022/04/17/16157444.html
-Advertisement-
Play Games

線程通信 1、場景:生產者和消費者問題 ==倉庫、生產者、消費者 倉庫只能存放一個產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走 如果倉庫中沒有產品,生產者將產品放入倉庫,否則停止生產並等待(阻塞),直到倉庫中的產品被消費者取走 如果倉庫中有產品,消費者可以將產品取走消費,否則停止消費 ...


線程通信

1、場景:生產者和消費者問題

==倉庫、生產者、消費者

====倉庫只能存放一個產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走

====如果倉庫中沒有產品,生產者將產品放入倉庫,否則停止生產並等待(阻塞),直到倉庫中的產品被消費者取走

====如果倉庫中有產品,消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品為止

生產者和消費者共用一個資源,並且生產者和消費者之間相互依賴,互為條件。

==生產者消費者問題中,僅有synchronized是不夠的,

synchronized可以阻止併發更新同一個共用資源,實現了同步,但不能用來實現不同線程之間的消息傳遞(通信)

2、線程通信的操作方法

(Object類的方法,只能在同步方法或同步代碼塊中使用,否則會拋出異常llegalMonitorStateException)

(1)wait() :線程一直等待,直到其它線程通知,會釋放鎖

(2)wait(long timeout):指定等待的毫秒數

(3)notify():喚醒一個處於等待狀態的線程

(4)notifyAll():喚醒同一個對象上所有調用wait()方法的線程,優先順序高的線程先調度。

3、線程通信的方式:

(1)管程法(生產者 緩衝區 消費者)

併發協作模型:生產者消費者模式 管程法

==生產者:負責生產數據的模塊(方法、對象、線程、進程)

==消費者:負責處理數據的模塊(方法、對象、線程、進程)

==緩衝區:消費者不直接使用生產者的數據,生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據

//測試生產者消費者模式  :    管程法 (生產者 消費者  緩衝區)
public class TestPC {
   public static void main(String[] args) {
       SynContainer synContainer = new SynContainer();
       new Productor(synContainer).start();
       new Consumer(synContainer).start();
  }
}
//生產者
class Productor extends Thread{
  SynContainer container;
  public Productor(SynContainer container){
      this.container = container;
  }
   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           Chicken chicken = new Chicken(i);
           container.push(chicken);
           System.out.println("productor product no."+i+"chicken");
      }
  }
}
//消費者
class Consumer extends Thread{
   SynContainer container;
   public Consumer(SynContainer container){
       this.container = container;
  }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println("no."+container.pop().num+"chicken ConSume");
      }
  }
}
//產品
class Chicken{
   int num;

   public Chicken(int num) {
       this.num = num;
  }
}
//緩衝區
class SynContainer{
   //容器大小
   Chicken[] chickens = new Chicken[10];
   //數量
   int count =0;

   //生產者生產 放入
   public synchronized void push(Chicken chicken){
       //如果容器滿了
       if (count==chickens.length){
           //產品滿了通知消費者消費
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       //如果沒滿 則生產 進入緩衝區
       chickens[count]=chicken;
       count++;

       //生產者每生產產品就可以 通知消費者消費
       this.notifyAll();
  }

   //消費者消費 取出
   public synchronized Chicken pop(){
       //如果容器為空
       if (count==0){
           //產品為空 通知生產者生產
           try {
               this.wait();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       //消費者消費
       count--;
       Chicken chicken=chickens[count];
       //消費者每次消費 則通知生產者生產
       this.notifyAll();

       return chicken;
  }
}

(2)信號燈法(標誌位)

添加標誌位來判斷

//生產者消費者模式 :  信號燈法  設置一個標誌位
public class TestPC2 {
   public static void main(String[] args) {
       TV tv = new TV();
       new Player(tv).start();
       new Watcher(tv).start();
  }
}
//生產者   演員
class Player extends Thread{
   TV tv;
   public Player(TV tv){
       this.tv = tv;
  }

   @Override
   public void run() {
       for (int i = 0; i < 10; i++) {
           if (i%2==0){
               this.tv.play("琅琊榜");
          }else {
               this.tv.play("周生如故");
          }
      }
  }
}
//消費者   觀眾
class Watcher extends Thread{
  TV tv;
  public Watcher(TV tv){
      this.tv= tv;
  }

   @Override
   public void run() {
       for (int i = 0; i < 20; i++) {
           this.tv.watch();
      }
  }
}
//產品   節目
class TV{
   //演員表演 觀眾等待 T
   //觀眾觀看 演員等待 F
   String voice; //表演的節目
   boolean flag = true;//設置的標誌位

   //表演
   public synchronized void play(String voice){
       //如果flag 不為true 代表觀眾還在觀看
       if (!flag){
           try {
               this.wait(); //這個線程等待
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       // 為true 則可以開始表演
       System.out.println("表演了"+voice);
       //通知觀眾觀看
       this.notifyAll();//通知喚醒所有等待的線程
       this.voice = voice;
       this.flag=!this.flag; //改變標誌位 代表表演結束 等觀眾觀看
  }

   public synchronized void watch(){
       //如果flag 為true 則代表演員還在表演
       if (flag){
           try {
               this.wait(); //這個線程等待
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
       System.out.println("觀眾觀看了"+voice);
       //通知演員表演
       this.notifyAll();//通知喚醒
       this.flag = !this.flag;//改變標誌位 代表觀看結束 等演員表演
  }

}

(3)線程池

 

 

 

 

public class TestPool {
   public static void main(String[] args) {
       MyThread myThread = new MyThread();
       //1、創建服務 創建線程池
       // newFixedThreadPool 線程池的大小為 10
       ExecutorService executorService = Executors.newFixedThreadPool(10);
       ExecutorService service = Executors.newFixedThreadPool(4);

       //2、執行 放入線程池中
       executorService.execute(myThread);
       executorService.execute(myThread);
       executorService.execute(myThread);
       service.execute(myThread);
       service.execute(myThread);
       service.execute(myThread);

       //3、關閉服務 關閉線程池連接
       executorService.shutdown();
       service.shutdown();
  }
}

class MyThread implements Runnable{
   @Override
   public void run() {
       for (int i = 0; i < 2; i++) {
           System.out.println(Thread.currentThread().getName()+i);
      }


  }
}

 

8鎖問題

1、標準情況下兩個線程哪個先列印

(鎖的是同一對象,先拿到鎖先執行)

public class aTest {
   public static void main(String[] args) {
       // 鎖的是同一對象 phone
       // send 先拿到鎖先執行   call 後拿到鎖後執行
       Phone phone = new Phone();
       new Thread(()->{
           phone.send();
      }).start();

       try {
           TimeUnit.SECONDS.sleep(1);//休眠1秒
      } catch (InterruptedException e) {
           e.printStackTrace();
      }

       new Thread(()->{
           phone.call();
      }).start();
  }
}
//synchronized 鎖的是同一對象 Phone
class Phone{
   public synchronized void send(){
       System.out.println("發簡訊");
  }

   public synchronized void call(){
       System.out.println("打電話");
  }
}

2、有一個線程延遲,另一個線程正常,哪個先列印

(鎖的還是同一對象,先拿到鎖先執行(雖然有send延遲,但是同一對象內的,還是先執行))

//先send 再call
public class bTest {
   public static void main(String[] args) throws InterruptedException {
       Phone2 phone = new Phone2();
       new Thread(()->{phone.send();}).start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{phone.call();}).start();
  }
}
class Phone2{
   public synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("發簡訊");
  }
   public synchronized void call(){
       System.out.println("打電話");
  }
}

兩個線程是同一對象調用方法,方法都有synchronized修飾,則鎖的是這個對象(相當於鎖粗化了) 這個對象的鎖是同一把鎖 調用的方法即線程 誰先拿到鎖誰先執行(相當於 一次只能有一個線程調用該方法)

3、在兩個同步方法情況下,增加一個普通方法,調用同步方法和普通方法,哪個先執行?(普通方法先執行,因為不受鎖的影響)

//先hello再send、call
public class cTest {
   public static void main(String[] args) throws InterruptedException {
       Phone3 phone = new Phone3();

       new Thread(()->{phone.send();}).start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{phone.call();}).start();
       new Thread(()->{phone.hello();}).start();
  }
}
class Phone3{
   public synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("發簡訊");
  }
   public synchronized void call(){
       System.out.println("打電話");
  }
   public void hello(){
       System.out.println("hello");
  }
}

4、創建兩個對象,分別調用同步方法,哪個先執行?

(創建兩個對象,synchronized鎖的是方法的調用者,即這個兩個對象都有鎖,共兩把鎖,沒有線程休眠的先執行)

//先call再send(有休眠)
public class dTest {
   public static void main(String[] args) throws InterruptedException {
       Phone4 phone1 = new Phone4();//對象1
       Phone4 phone2 = new Phone4();//對象2
       new Thread(()->{phone1.send(); }).start();
       TimeUnit.SECONDS.sleep(1);
//       new Thread(()->{phone2.hello();}).start();
       new Thread(()->{phone2.call();}).start();
//       new Thread(()->{phone1.hello();}).start();
  }

}
class Phone4{
   public synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("發簡訊");
  }
   public synchronized void call(){
       System.out.println("打電話");
  }
   //普通方法 沒加鎖 非同步方法
   public void hello(){
       System.out.println("hello");
  }
}

5、都是靜態同步方法static synchronized修飾,哪個先執行?

(按類的方法的順序來執行)

(static修飾的方法,在類載入時就存在了,鎖的是類class,在對象調用時,進入的是類的鎖)

//先send再call
public class eTest {
   public static void main(String[] args) throws InterruptedException {
       Phone5 phone = new Phone5();
       new Thread(()->{phone.send();}).start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{phone.call();}).start();
  }
}
class Phone5{
   /* static 修飾的方法 在類載入的時候就創建了就有了
      也就是 靜態按順序(模板)固定了   鎖的是類 Class
        在對象調用時 就進入的是類的鎖 Class的鎖
   */
   public static synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("發簡訊");
  }
   public static synchronized void call(){
       System.out.println("打電話");
  }
}

6、兩個對象分別調用兩個靜態同步方法,哪個先執行?

(按class類中的方法調用順序執行)

(因為兩個對象是由同一個類創建的,這個class的方法是靜態同步方法,鎖的是這個class,所以無論從這個class中創建多少個對象,調用class靜態同步方法都是按class中的方法的調用順序的,同一把鎖class,先調用的方法,先得到鎖)

//先send 後 call
public class fTest {
   public static void main(String[] args) {
       Phone6 phone1 = new Phone6();
       Phone6 phone2 = new Phone6();
       new Thread(()->{phone1.send();}).start();
       new Thread(()->{phone2.call();}).start();
  }
}
class Phone6{
   public static synchronized void send(){
       try {
           TimeUnit.SECONDS.sleep(4);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println("發簡訊");
  }
   public static synchronized void call(){
       System.out.println("打電話");
  }
}

7、一個靜態同步方法send和一個普通同步call方法,一個對象,哪個先執行?

(靜態同步方法send 先執行)

(靜態同步方法鎖的是class類,普通同步方法鎖的是對象)

//先call再send
public class gTest {
   public static void main(String[] args) throws InterruptedException {
       Phone7 phone = new Phone7();
       new Thread(()->{phone.send();}).start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{phone.call();}).start();
  }
}
class Phone7{
   public static synchronized void send(){
       /*如果有進行休眠,就會先休眠,對象鎖先執行
       try {
           TimeUnit.SECONDS.sleep(4);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       */
       System.out.println("發簡訊");
  }
   public synchronized void call(){
       System.out.println("打電話");
  }
}

8、一個靜態同步方法send,一個普通同步方法call,兩個對象,哪個先執行?

(看執行順序)

(靜態同步方法鎖的是class類,普通同步方法鎖的是對象)

//先send再call
public class hTest {
   public static void main(String[] args) throws InterruptedException {
       Phone8 phone1 = new Phone8();
       Phone8 phone2 = new Phone8();
       new Thread(()->{phone1.send();}).start();
       TimeUnit.SECONDS.sleep(1);
       new Thread(()->{phone2.call();}).start();
  }
}
class Phone8{
   public static synchronized void send(){
       /*如果有進行休眠,就會先休眠,對象鎖先執行
       try {
           TimeUnit.SECONDS.sleep(4);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       */
       System.out.println("發簡訊");
  }
   public synchronized void call(){
       System.out.println("打電話");
  }
}
 
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 網頁Cytus Ⅱ的製作 整體佈局 單欄佈局,整體按順序分為以下幾個板塊: 頁首下載、新聞表單、角色輪播圖、視頻、頁尾以及側邊欄 要儘可能把自己的所有知識用上,所以可能會有和原網頁寫法不同,但基本樣式差不多 頁首下載 背景圖片 .head { width: 100%; height: 960px; ...
  • ##背景 封樓期間難得空閑,也靜不下心學習,空閑之餘萌生了重做引導單頁的想法。因為之前都是扒站(某大公司游戲官網)+小改,一來雖然很炫酷,但本人水平有限,仍有很大一部分JS無從下手,甚至是看不懂|-_-|;二來對方畢竟沒有開源,無論道德還是法律都說不過去,所以……先從簡單處寫起,後續慢慢迭代吧! # ...
  • 策略模式是什麼 策略模式是一種行為設計模式, 它能讓你定義一系列演算法, 並將每種演算法分別放入獨立的類中, 以使演算法的對象能夠相互替換。 為什麼用策略模式 當你想使用對象中各種不同的演算法變體,並希望能在運行時切換演算法時,可使用策略模式。策略模式讓你能將不同行為抽取到一個獨立類層次結構中, 並將原始類組 ...
  • 函數是帶名字的代碼塊,要執行函數定義的特定任務,可調用該函數。 需要在程式中多次執行同一項任務時,你無需反覆編寫完成該任務的代碼,而只需調用執行該任務的函數,通過使用函數,程式的編寫、閱讀、測試和修複都將更容易。主程式文件的組織更為有序 一、如何定義一個函數 使用關鍵字 def 來定義一個函數。 d ...
  • 簡介 通過annotation像強類型language那樣指定變數類型,包括參數和返回值的類型 因為Python是弱類型語言,這種指定實際上無效的。所以這種寫法叫annotation,就是個註釋參考的作用。通過annotation可以極大的提升代碼可讀性 語法為“var_name: type [= ...
  • 本文將以單鏈表和靜態鏈表的初始化代碼(c++)為例,具體分析了結構體中typedef struct LNode{....} LNode, *LinkList, SLinkList[MaxSize];的相關問題,並補充了C++中引用類型的一點知識。 ...
  • https://www.cnblogs.com/yeungchie/ 記錄一些常用的 模塊 / 方法 。 多線程 使用模塊 threads use 5.010; use threads; # 定義一個需要併發的子函數 sub func { my $id = shift; sleep 1; print ...
  • 測評目錄 python技能樹測評 python技能樹是什麼 python技能樹長什麼樣 如何學習python技能樹 python技能樹可能需要的改進 對python技能樹的總結 CSDN MarkDown編輯器測評 python技能樹測評 CSDN上線了一個叫python技能樹的產品,主要是用來作為 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...