線程(二)__線程間的通信

来源:http://www.cnblogs.com/xiangkejin/archive/2016/10/14/5962283.html
-Advertisement-
Play Games

線程間的通信:多個線程在處理同一資源,但是任務卻不同。一、等待喚醒機制涉及的方法:1.wait();讓線程處於凍結狀態,被wait的線程會被存儲到線程池中2.notify();喚醒線程池中的一個任意線程3.notifyAll();喚醒線程池中的所有線程這些方法都必須定義在同步中,因為這些方法是用於操 ...


線程間的通信:多個線程在處理同一資源,但是任務卻不同。
一、等待喚醒機制
涉及的方法:
1.wait();讓線程處於凍結狀態,被wait的線程會被存儲到線程池中
2.notify();喚醒線程池中的一個任意線程
3.notifyAll();喚醒線程池中的所有線程
這些方法都必須定義在同步中,因為這些方法是用於操作線程狀態的方法,必須要明確到底操作的是哪個鎖上的線程
wait()對A鎖上面的線程進行操作後只能用A鎖的notify來喚醒。被wait之後的線程可認為放線上程池中。

為什麼操作線程的方法wait notify notifyAll定義在Object類中?
因為這些方法是監視器的方法,監視器其實就是鎖。
鎖可以是人一多對象,任意的對象調用的方式一定是定義在Object中。

package com.test2;

class Resource
{
    private String name;
    private  int count=1;
    private  boolean flag=false;

    public synchronized void set(String name)
    {
       if(flag)
            try{ this.wait();}catch(InterruptedException e){}

        this.name=name+count;
        count++;
        System.out.println(Thread.currentThread().getName() + "...." + "生產者"+".........." + this.name);
       flag=true;
        notify();
    }

    public synchronized void out()
    {
        if(!flag)
        try{ this.wait();}catch(InterruptedException e){}

        System.out.println(Thread.currentThread().getName()+"...."+"消費者"+"...."+this.name);
        flag=false;
        notify();
    }
}

class Producer implements  Runnable
{ private Resource r;
    Producer(Resource r)
    {
        this.r =r;
    }
    public void run()
    {
        while(true)
        {
            r.set("烤鴨");
        }
    }

}

class Consumer implements  Runnable
{ private Resource r;
    Consumer(Resource r)
    {
        this.r =r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }

}

public class Demo
{
    public static void main(String[] args)
    {
        Resource r=new Resource();

        Producer a=new Producer(r);
        Consumer b=new Consumer(r);
        
        Thread t0=new Thread(a);      
        Thread t1=new Thread(b);
       
        t0.start();
        t1.start();            
    }
} 

產生的結果是:每生產一隻就消費一隻。

 

二、多生產者多消費者問題

將代碼改成兩個生產者兩個消費者:

Thread t0=new Thread(a);
Thread t1=new Thread(a);
Thread t2=new Thread(b);
Thread t3=new Thread(b);

t0.start();
t1.start();
t2.start();
t3.start();

可見還是產生了安全問題,關鍵在於這段代碼中:

  if(flag)

     try{ this.wait();}catch(InterruptedException e){}  //t0 t1

當t0 t1被wait()掛在那後當再次喚醒的時候不會再次去判斷flag標記,而直接往下走再次去生產,導致發生錯誤。

只要將if改為while語句讓它返回再次判斷一次即可。

  while(flag)

     try{ this.wait();}catch(InterruptedException e){}  

可是將代碼改成這樣後,卻出現了死鎖問題:

結果運行到這就卡死不動了。
原因:當走到t0(活),t1,t2,t3的情況時,t0走完線程代碼,喚醒不是t2或者t3中間的一個而是t1,標誌位也改為那麼true,那麼t1也被直接wait了。
現在t0,t1,t2,t3全死,不會再喚醒,出現死鎖。而上面沒有出現的死鎖的原因在於用if語句,喚醒之後程式接著往下走,總會notify任何一個線程而不會
把所有線程都被wait,而用了while當喚醒之後首先判斷標誌位,會直接掛死(喚醒的是同一方的線程)。
所以究其原因是沒有喚醒對方的線程。那麼怎麼保證每次都能至少喚醒對方的一個線程呢?
很遺憾,可是沒有辦法喚醒指定的線程,可以考慮將notify改為notifyAll每次喚醒所有wait的線程可以解決問題,搞定!再次會每生產一個就會消費一個。

總結:
if判斷標記,只有一次,會導致不該運行的線程運行了,出現數據錯誤的情況。

   while判斷標記,解決線程獲取執行權後是否要運行。

   notifyAll解決了本方線程一定會喚醒對方線程,notify可能只是喚醒了本方線程,沒有意義。且while標記+notify會導致死鎖。

   所以只要是多生產者多消費者的情況,就用while+notifyAll。
 

問題:在用notifyAll喚醒線程時可能喚醒了本方線程,可是喚醒本方線程是沒有意義的(效率較低),本方線程已經幹完活了,需要喚醒對方線程幹活就行了。

在jdk1.5 java.util.concurrent.locks中提供了介面Lock

Lock實現提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。

在同步代碼塊中,對於鎖的操作是隱式的,獲得和釋放都是隱式。jdk1.5後將鎖和同步封裝成對象,按面向對象的方式顯示操作鎖。

Object obj=new Object();

void show ()

{  synchronized(obj)

     {                              

  }

}

變為:

Lock lock=new ReetrantLock();     //互斥鎖,被一個線程獲取後不能被其他線程獲取。

void show ()

{ lock.lock();//獲得鎖

        code。。。

   lock.unlock();//釋放鎖

}

介面Conditionn將Object監視器方法(wait、notify、notifyAll)分解成截然不同的對象,以便通過這些對象與任意的lock組合使用。

其中Lock替代了synchronized方法與語句的使用,conditiont替代了Object監視器方法的使用。

Condition實例實質上被綁定在一個鎖上,要為Lock實例獲得Condition實例使用newCondition()方法。

 

對於上面問題的解決方法:生產者和消費者分別獲取一個condition對象,各自擁有一組監視器方法。生產者指定喚醒消費者,消費者指定喚醒生產者。


改動的代碼如下:
class Resource1 {
    private String name;
    private int count = 1;
    private boolean flag = false;
    //創建一個鎖對象。
    Lock lock = new ReentrantLock();
    //通過已有的鎖獲取該鎖上的監視器對象
   // Condition con = lock.newCondition();

    //通過已有的鎖獲取兩組監視器,一組監視生產者,一組監視消費者
    Condition producer_con=lock.newCondition();
    Condition consumer_con=lock.newCondition();


    public void set(String name) {
        lock.lock();
        try {
            while (flag)  //if()
                try {
                    producer_con.await();
                } catch (InterruptedException e) {}

            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName() + "...." + "生產者" + "...." + this.name);
            flag = true;
            consumer_con.signal();//notify()


        } finally {
            lock.unlock();
        }
    }

    public void out() {
        lock.lock();
        try {
            while (!flag)  //if()
                try {
                    consumer_con.await();
                } catch (InterruptedException e) {}

            System.out.println(Thread.currentThread().getName() + "...." + "消費者" + "...." + this.name);
            flag = false;
            producer_con.signal();//notify()
        } finally {
            lock.unlock();
        }

    }
}

class Producer1 implements  Runnable
{ private Resource r;
    Producer1(Resource r)
    {
        this.r =r;
    }
    public void run()
    {
        while(true)
        {
            r.set("烤鴨");
        }
    }

}

class Consumer1 implements  Runnable
{ private Resource r;
    Consumer1(Resource r)
    {
        this.r =r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }

}

public class LockDemo
{
    public static void  main(String[] args)
    {
        Resource r=new Resource();

        Producer a=new Producer(r);
        Consumer b=new Consumer(r);

        Thread t0=new Thread(a);
        Thread t1=new Thread(a);
        Thread t2=new Thread(b);
        Thread t3=new Thread(b);

        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}
View Code

總結:

* Lock介面:出現替代了同步代碼塊或者同步函數。將同步隱式操作變成顯示鎖操作。同時更加靈活。可以一個鎖上加上多組監視器。
* lock():獲取鎖
* unlock():釋放鎖,通常要要定義在finally代碼塊當中。
* Condition介面:出現替代了Object中wait notify notifyAll方法,這些監視器方法單獨進行了封裝,變成Condition監視器對象。
* 可以任意的鎖進行組合。
* await()——wait()
* signal()——notify()
* signalAll()——notifyAll()

 

示例假定有一個綁定的緩衝區,它支持 puttake 方法。如果試圖在空的緩衝區上執行 take 操作,則在某一個項變得可用之前,線程將一直阻塞;

如果試圖在滿的緩衝區上執行 put 操作,則在有空間變得可用之前,線程將一直阻塞。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

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

-Advertisement-
Play Games
更多相關文章
  • NPOI 2.0+主要由SS, HPSF, DDF, HSSF, XWPF, XSSF, OpenXml4Net, OpenXmlFormats組成,具體列表如下: 資料來自:百度百科 NPOI.POIFS OLE2/ActiveX文檔屬性讀寫庫 NPOI.DDF 微軟Office Drawing讀 ...
  • 一:什麼是∧運算符: 二元 ^ 運算符是為整型和 bool 類型預定義的。對於整型,^ 將計算操作數的按位“異或”。對於 bool 操作數,^ 將計算操作數的邏輯“異或”;也就是說,當且僅當只有一個操作數為 true 時,結果才為 true。 二:在進行異或運算規則: 如兩個二進位數的相應位都為1 ...
  • 這兩天看到同事的一個小工具,用的是模塊式開發,也就是俗稱的插件開發,用的是反射+介面的方式實現的。感覺挺好的,也就學習了一下,寫個小Demo,在此記錄下。 一、寫介面類 介面類是所有模塊的基礎,因為讓主程式去尋找模塊,就是通過反射來找到繼承此介面的相關項目,也就是後期包含繼承此介面類的DLL文件。 ...
  • 當初說這個需求的時候,在網上找了一點資料,但是基本上感覺不符合項目中的需求。參照一些項目,和同事的改造,終於是像點樣子了 截圖大致截為3個像素,每個像素使用的地方也不同,考慮圖片不會是很多,分別壓縮保存下來。 根據截取的像素位置,對應的壓縮成相應的圖片: 首先需要下載Jcrop.js與uploadi ...
  • 輸出那點兒事 printf函數是一個標準庫函數,能夠以精確的格式輸出程式運算的結果 printf函數的調用格式: 格式控制字元串 是由格式字元(包括:轉換說明符、標誌、域寬、精度)和普通字元組成,轉換說明符和 一起使用,用來說明輸出數據的數據類型、標誌、長度和精度 格式控制的完整格式: :表示格式說 ...
  • 一、定義 DLL是Dynamic Link Library的縮寫,意為動態鏈接庫。在Windows中,許多應用程式並不是一個完整的可執行文件,它們被分割成一些相對獨立的動態鏈接庫,即DLL文件,放置於系統中。當我們執行某一個程式時,相應的DLL文件就會被調用。一個應用程式可有多個DLL文件,一個DL ...
  • 一、Python的介紹 python的創始人為吉多.範羅蘇姆。1989年的聖誕期間,吉多.範羅蘇姆為了在阿姆斯特丹打發時間,決心開發一個新的腳本解釋程式,作為ABC語言的一種繼承。 二、Python是一門什麼樣的語言? 編程語言主要是從以下幾個角度進行分類,編譯型和解釋型、靜態語言和動態語言、強類型 ...
  • 一直以來把資料庫的表轉換成Entity或DTO都是一件讓人頭痛的事情,既浪費時間又很繁瑣,用其他工具生成多少會有一些不盡人意的地方,於是就自己用Swing寫了一個通過資料庫的表生成JavaBean的工具,支持MySQL、Oracle、SQLServce、PostgreSQL,完美支持JPA註解,可以... ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...