中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中怎么實現多線程通信

發布時間:2021-07-29 16:55:01 來源:億速云 閱讀:167 作者:Leah 欄目:開發技術

Java中怎么實現多線程通信,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

概述

多線程通信問題,也就是生產者與消費者問題

生產者和消費者為兩個線程,兩個線程在運行過程中交替睡眠,生產者在生產時消費者沒有在消費,消費者在消費時生產者沒有在生產,確保數據安全

以下為百度百科對于該問題的解釋:

生產者與消費者問題:
生產者消費者問題(Producer-consumer problem),也稱有限緩沖問題(Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共享固定大小緩沖區的線程——即所謂的“生產者”和“消費者”——在實際運行時會發生的問題。生產者的主要作用是生成一定量的數據放到緩沖區中,然后重復此過程。與此同時,消費者也在緩沖區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據。

解決辦法:
要解決該問題,就必須讓生產者在緩沖區滿時休眠(要么干脆就放棄數據),等到下次消費者消耗緩沖區中的數據的時候,生產者才能被喚醒,開始往緩沖區添加數據。同樣,也可以讓消費者在緩沖區空時進入休眠,等到生產者往緩沖區添加數據之后,再喚醒消費者。通常采用進程間通信的方法解決該問題,常用的方法有信號燈法等。如果解決方法不夠完善,則容易出現死鎖的情況。出現死鎖時,兩個線程都會陷入休眠,等待對方喚醒自己。該問題也能被推廣到多個生產者和消費者的情形。

引入

該過程可以類比為一個栗子:

廚師為生產者,服務員為消費者,假設只有一個盤子盛放食品。

廚師在生產食品(廚師線程運行)的過程中,服務員應當等待(服務員線程睡眠),等到食品生產完成(廚師線程結束)后將食品放入盤子中,服務員將盤子端出去(服務員線程運行),此時沒有盤子可以放食品,因此廚師休息(廚師線程休眠),一段時間過后服務員將盤子拿回來(服務員線程結束),廚師開始進行生產食品(廚師線程運行),服務員在一旁等待(服務員線程睡眠)…

在此過程中,廚師和服務員兩個線程交替睡眠,廚師在做飯時服務員沒有端盤子(廚師線程運行時服務員線程睡眠),服務員在端盤子時廚師沒有在做飯(服務員線程運行時廚師線程睡眠),確保了數據的安全

根據廚師和服務員這個栗子,我們可以通過代碼來一步步實現

  • 定義廚師線程

 /**
     * 廚師,是一個線程
     */
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f){
            this.f = f;
        }
        //運行的線程,生成100道菜
        @Override
        public void run() {
            for (int i = 0 ; i < 100; i ++){
                if(i % 2 == 0){
                    f.setNameAneTaste("小米粥","沒味道,不好吃");
                }else{
                    f.setNameAneTaste("老北京雞肉卷","甜辣味");
                }
            }
        }
    }
  • 定義服務員線程

/**
     * 服務員,是一個線程
     */
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }
        @Override
        public void run() {
            for(int i =0 ; i < 100;i ++){
                //等待
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }//end run
    }//end waiter
  • 新建食物類

 /**
     * 食物,對象
     */
    static class Food{
        private String name;
        private String taste;
        public void setNameAneTaste(String name,String taste){
            this.name = name;
            //加了這段之后,有可能這個地方的時間片更有可能被搶走,從而執行不了this.taste = taste
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;
        }//end set
        public void get(){
            System.out.println("服務員端走的菜的名稱是:" + this.name + " 味道:" + this.taste);
        }
    }//end food

main方法中去調用兩個線程

public static void main(String[] args) {
        Food f = new Food();
        Cook c = new Cook(f);
        Waiter w = new Waiter(f);
        c.start();//廚師線程
        w.start();//服務生線程     
    }

運行結果:

只截取了一部分,我們可以看到,“小米粥”并沒有每次都對應“沒味道,不好吃”,“老北京雞肉卷”也沒有每次都對應“甜辣味”,而是一種錯亂的對應關系

...
服務員端走的菜的名稱是:老北京雞肉卷 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
...

name和taste對應錯亂的原因:

當廚師調用set方法時,剛設置完name,程序進行了休眠,此時服務員可能已經將食品端走了,而此時的taste是上一次運行時保留的taste。

兩個線程一起運行時,由于使用搶占式調度模式,沒有協調,因此出現了該現象

以上運行結果解釋如圖:

Java中怎么實現多線程通信

加入線程安全

針對上面的線程不安全問題,對廚師set和服務員get這兩個線程都使用synchronized關鍵字,實現線程安全,即:當一個線程正在執行時,另外的線程不會執行,在后面排隊等待當前的程序執行完后再執行

代碼如下所示,分別給兩個方法添加synchronized修飾符,以方法為單位進行加鎖,實現線程安全

	/**
     * 食物,對象
     */
    static class Food{
        private String name;
        private String taste;
        public synchronized void setNameAneTaste(String name,String taste){
            this.name = name;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;
        }//end set
        public synchronized void get(){
            System.out.println("服務員端走的菜的名稱是:" + this.name + " 味道:" + this.taste);
        }
    }//end food

輸出結果:

由輸出可見,又出現了新的問題:
雖然加入了線程安全,set和get方法不再像前面一樣同時執行并且菜名和味道一一對應,但是set和get方法并沒有交替執行(通俗地講,不是廚師一做完服務員就端走),而是無序地執行(廚師有可能做完之后繼續做,做好幾道,服務員端好幾次…無規律地做和端)

...
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:小米粥 味道:沒味道,不好吃
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
服務員端走的菜的名稱是:老北京雞肉卷 味道:甜辣味
...

實現生產者與消費者問題

由上面可知,加入線程安全依舊無法實現該問題。因此,要解決該問題,回到前面的引入部分,嚴格按照生產者與消費者問題中所說地去編寫程序

生產者與消費者問題:
生產者和消費者為兩個線程,兩個線程在運行過程中交替睡眠,生產者在生產時消費者沒有在消費,消費者在消費時生產者沒有在生產,確保數據安全

廚師在生產食品(廚師線程運行)的過程中,服務員應當等待(服務員線程睡眠),等到食品生產完成(廚師線程結束)后將食品放入盤子中,服務員將盤子端出去(服務員線程運行),此時沒有盤子可以放食品,因此廚師休息(廚師線程休眠),一段時間過后服務員將盤子拿回來(服務員線程結束),廚師開始進行生產食品(廚師線程運行),服務員在一旁等待(服務員線程睡眠)…

在此過程中,廚師和服務員兩個線程交替睡眠,廚師在做飯時服務員沒有端盤子(廚師線程運行時服務員線程睡眠),服務員在端盤子時廚師沒有在做飯(服務員線程運行時廚師線程睡眠),確保數據的安全

需要用到的java.lang.Object 中的方法:

變量和類型方法描述
voidnotify()喚醒當前this下的單個線程
voidnotifyAll()喚醒當前this下的所有線程
voidwait()當前線程休眠
voidwait(long timeoutMillis)當前線程休眠一段時間
voidwait(long timeoutMillis, int nanos)當前線程休眠一段時間
  • 首先在Food類中加一個標記flag:

True表示廚師生產,服務員休眠

False表示服務員端菜,廚師休眠

private boolean flag = true;

對set方法進行修改

當且僅當flag為True(True表示廚師生產,服務員休眠)時,才能進行做菜操作

做菜結束時,將flag置為False(False表示服務員端菜,廚師休眠),這樣廚師在生產完之后不會繼續生產,避免了廚師兩次生產、服務員端走一份的情況

然后喚醒在當前this下休眠的所有進程,而廚師線程進行休眠

		public synchronized void setNameAneTaste(String name,String taste){
            if(flag){//當標記為true時,表示廚師可以生產,該方法才執行
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;//生產完之后,標記置為false,這樣廚師在生產完之后不會繼續生產,避免了廚師兩次生產、服務員端走一份的情況
                this.notifyAll();//喚醒在當前this下休眠的所有進程
                try {
                    this.wait();//此時廚師線程進行休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }//end set
  • 對get方法進行修改

當且僅當flag為False(False表示服務員端菜,廚師休眠)時,才能進行端菜操作

端菜結束時,將flag置為True(True表示廚師生產,服務員休眠),這樣服務員在端完菜之后不會繼續端菜,避免了服務員兩次端菜、廚師生產一份的情況

然后喚醒在當前this下休眠的所有進程,而服務員線程進行休眠

public synchronized void get(){
            if(!flag){//廚師休眠的時候,服務員開始端菜
                System.out.println("服務員端走的菜的名稱是:" + this.name + " 味道:" + this.taste);
                flag = true;//端完之后,標記置為true,這樣服務員在端完菜之后不會繼續端菜,避免了服務員兩次端菜、廚師只生產一份的情況
                this.notifyAll();//喚醒在當前this下休眠的所有進程
                try {
                    this.wait();//此時服務員線程進行休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }// end if
        }//end get


看完上述內容,你們掌握Java中怎么實現多線程通信的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

张北县| 会同县| 萝北县| 景德镇市| 永泰县| 东丽区| 六安市| 建平县| 新沂市| 丰宁| 洪雅县| 贞丰县| 湘阴县| 探索| 开封县| 兴海县| 金门县| 奉节县| 延寿县| 防城港市| 抚宁县| 句容市| 光山县| 东源县| 永靖县| 泾源县| 湟中县| 阿克陶县| 汾阳市| 伊宁县| 武清区| 乌什县| 岐山县| 改则县| 遂溪县| 中牟县| 凤凰县| 宣威市| 怀远县| 满洲里市| 兰州市|