(三)使用synchronized實現(xiàn)線程間的通信


線程與線程之間執(zhí)行的任務(wù)不同,但線程與線程之間操作的數(shù)據(jù)相同他炊。

1掏熬、搭建示例

實現(xiàn)多線程同時讀取并輸出學(xué)生的信息
1.創(chuàng)建一個類用于存放學(xué)生的信息秒梅;
2.創(chuàng)建一個類用于存放輸入任務(wù);
3.創(chuàng)建一個類用于存放輸出任務(wù)疮丛;
4.使用傳參的方式讓兩個線程使用相同的參數(shù),實現(xiàn)線程間的通信履恩。


// 描述數(shù)據(jù)呢蔫,封裝到類中
class Student {
    String name;
    String sex;
}

// 描述輸入任務(wù)
class Input implements Runnable {
    /*
     * 使用傳參的方式 
     * 保證2個不同的任務(wù) 
     * 操作相同的數(shù)據(jù)
     */
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
         // 為了避免數(shù)據(jù)被覆蓋,使用if條件處理
        int i = 1;
        // 為了保證存儲盡可能多的數(shù)據(jù)片吊,使用while循環(huán)
        while (true) {
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = "男";
                } else {
                    stu.name = "Monica";
                    stu.sex = "女";
                }
            // 保證i的改變,使i在1與0之間來回切換
            i = (i + 1) % 2;
        }
    }
}

// 描述輸出任務(wù)
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        // 使用while循環(huán)保證一直輸出
        while (true) {
                System.out.println(stu.sex + "生:" + stu.name);  
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {
        // 創(chuàng)建資源對象
        Student stu = new Student();
        // 創(chuàng)建輸入任務(wù)全谤,傳入?yún)?shù)stu
        Input in = new Input(stu);
        // 創(chuàng)建輸出任務(wù)爷贫,傳入?yún)?shù)stu
        Output out = new Output(stu);
        // 創(chuàng)建輸入線程
        Thread t1 = new Thread(in);
        // 創(chuàng)建輸出線程
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();

    }

}

此時結(jié)果如下:

之前存入的信息應(yīng)該是:"Tom"漫萄、"男","Monica"子刮、"女"窑睁,但結(jié)果卻出現(xiàn)了錯誤。出現(xiàn)這種錯誤的原因是:當輸入線程存儲完"Tom"橱赠、"男"之后箫津,向下運行,改變i的值為0饼拍,繼續(xù)循環(huán)田炭,當i=0時,剛儲存完"Monica"叨吮,此時CPU被輸出線程搶走,但輸入線程還未存儲"女"锋玲,因此輸出線程輸出了"Monica"涵叮、"男"。


2剿干、共享數(shù)據(jù)的安全問題

學(xué)生的信息在該示例代碼中就屬于共享數(shù)據(jù)穆刻,由輸入線程與輸出線程共同操作,如何實現(xiàn)數(shù)據(jù)安全榜轿,避免之前的錯誤朵锣,可以使用synchronized代碼塊包圍任務(wù)代碼,修改如下:

// 描述輸入任務(wù)
class Input implements Runnable {
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
        int i = 1;
        while (true) {
            //使用synchronized包圍輸入任務(wù)
            synchronized (stu) {
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = "男";
                } else {
                    stu.name = "Monica";
                    stu.sex = "女";
                }
            }
            i = (i + 1) % 2;
        }
    }
}

// 描述輸出任務(wù)
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        while (true) {
            //使用synchronized包圍輸出任務(wù)
            synchronized (stu) {
                System.out.println(stu.sex + "生:" + stu.name);
            }
        }
    }
}

此時結(jié)果如下:

使用synchronized實現(xiàn)同步鎖的兩個條件:第一是要有兩個或以上的線程存在,第二是這多個線程使用同一把鎖诬烹。 這里首先有輸入和輸出兩個線程绞吁,其次,線程使用的鎖都是stu對象家破,實現(xiàn)了鎖的統(tǒng)一汰聋,保證了效果。


3烹困、設(shè)置線程等待與線程喚醒

根據(jù)修改后的結(jié)果,雖然錯誤消除了措近,但是會持續(xù)長時間輸出同一個學(xué)生信息瞭郑,出現(xiàn)這種情況的原因是輸出線程一直占據(jù)CPU進行輸出鸭你,這樣會造成性能的浪費,為了避免這種情況阁谆,實現(xiàn)當存入一個數(shù)據(jù)時愉老,再輸出一個數(shù)據(jù),可以使用等待喚醒機制焰盗,代碼如下:


// 描述數(shù)據(jù)咒林,封裝到類中
class Student {
    String name;
    String sex;
    /*
     * 創(chuàng)建判斷是否繼續(xù)輸入或輸出的標記
     * 每當完成一次輸入或輸出修改flag的值
     * 此時默認為false
     */
    boolean flag;
}

// 描述輸入任務(wù)
class Input implements Runnable {
    private Student stu;
    public Input(Student stu) {
        this.stu = stu;
    }
    public void run() {
        int i = 1;
        while (true) {
            synchronized (stu) {
                /*
                 * 需要先判斷是否能存入數(shù)據(jù)
                 * 如果flag為true
                 * 執(zhí)行等待
                 * 線程就停止在此處
                 * 不會執(zhí)行后續(xù)代碼
                 * 如果flag為false
                 * 則會跳過wait()方法
                 * 執(zhí)行存入數(shù)據(jù)
                 * 因此不需要添加else
                 */
                if(stu.flag){
                    try {
                        /*
                         * wait()方法要求處理異常
                         * 因為在run()方法內(nèi)部
                         * 只能使用try-catch塊包圍
                         * 不能拋出
                         * wait()方法還必須用在同步代碼中
                         * 使用鎖垫竞,也就是對象去調(diào)用該方法
                         * 也就是讓持有stu這把鎖的線程進入等待
                         * 之后等待的線程會放棄鎖
                         */
                        stu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i == 1) {
                    stu.name = "Tom";
                    stu.sex = "男";
                } else {
                    stu.name = "Monica";
                    stu.sex = "女";
                }
                // 改為適用于另一線程的flag的值
                stu.flag = true;
                /*
                 * 執(zhí)行喚醒另一線程
                 * 此時可以為空喚醒
                 * 同wait()方法一樣
                 * notify()也必須用在同步代碼中
                 * 需要用鎖去調(diào)用
                 * 也就是喚醒持有stu這把鎖的線程
                 */
                stu.notify();
            }
            i = (i + 1) % 2;
        }
    }
}

// 描述輸出任務(wù)
class Output implements Runnable {
    private Student stu;
    public Output(Student stu) {
        this.stu = stu;
    }
    public void run() {
        while (true) {
            synchronized (stu) {
                /*
                 * 需要先判斷是否能輸出
                 * 之前將flag的值改為true
                 * 因此如果判斷為false
                 * 執(zhí)行等待
                 * 如果判斷為ture
                 * 執(zhí)行輸出數(shù)據(jù)
                 */
                if(!stu.flag){
                    try {
                        stu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(stu.sex + "生:" + stu.name);
                // 改為適用于另一線程的flag的值
                stu.flag = false;
                // 執(zhí)行喚醒另一線程欢瞪,此時可以為空喚醒
                stu.notify();
            }
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {
        // 創(chuàng)建資源對象
        Student stu = new Student();
        // 創(chuàng)建輸入任務(wù)
        Input in = new Input(stu);
        // 創(chuàng)建輸出任務(wù)
        Output out = new Output(stu);
        // 創(chuàng)建輸入線程
        Thread t1 = new Thread(in);
        // 創(chuàng)建輸出線程
        Thread t2 = new Thread(out);
        t1.start();
        t2.start();
    
    }

}

此時結(jié)果如下:

使用等待喚醒機制后遣鼓,實現(xiàn)了存儲一個數(shù)據(jù)后再輸出一個數(shù)據(jù)的效果,再簡單總結(jié)一下等待喚醒機制:

wait()方法宫补、notify()方法曾我、notifyAll()方法都必須用在同步代碼中,只有在同步代碼中才有鎖贫贝,而這3個方法都需要使用鎖來調(diào)用,哪個線程持有該鎖崇堵,哪個線程就會進入等待或喚醒狀態(tài)客燕。

  • ** wait()方法:**讓線程進入等待狀態(tài),實際上是將線程放入了線程池赏廓;
  • notify()方法:喚醒線程池中的任意一個線程傍妒,雖然是持有特定鎖的線程,但由于多個線程可以持有相同的鎖既忆,因此實際上是喚醒任意一個持有特定鎖的線程嗦玖;
  • notifyAll()方法:喚醒所有線程。

之所以將這三個方法定義在Object類中庆亡,是因為這三個方法都需要鎖來調(diào)用捞稿,而任意一個對象都可以當做鎖,也就意味著任意一個對象都可以調(diào)用這三個方法彰亥,因此只能定義在所有類的父類衰齐,即Object類中。


4废酷、優(yōu)化完善示例代碼


class Student {
    private String name;
    private String sex;
    private boolean flag;

    // 創(chuàng)建存儲數(shù)據(jù)方法
    public synchronized void set(String name, String sex) {
        if (flag) {
            try {
                wait();// 因為此時鎖是this抹缕,可以省略
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        this.sex = sex;
        flag = true;
        notify();
    }

    // 創(chuàng)建輸出數(shù)據(jù)方法
    public synchronized void out() {
        if (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(sex + "生:" + name);
        flag = false;
        notify();
    }
}

// 描述輸入任務(wù)
class Input implements Runnable {
    private Student stu;

    public Input(Student stu) {
        this.stu = stu;
    }

    public void run() {
        int i = 1;
        while (true) {
            if (i == 1) {
                stu.set("Tom", "男");
            } else {
                stu.set("Monica", "女");
            }
            i = (i + 1) % 2;
        }
    }
}

// 描述輸出任務(wù)
class Output implements Runnable {
    private Student stu;

    public Output(Student stu) {
        this.stu = stu;
    }

    public void run() {
        while (true) {
            stu.out();
        }
    }
}

public class Demo1 {

    public static void main(String[] args) {

        Student stu = new Student();

        Input in = new Input(stu);
        Output out = new Output(stu);

        Thread t1 = new Thread(in);
        Thread t2 = new Thread(out);

        t1.start();
        t2.start();

    }

}

注意:
隨著JKD版本的更新趴俘,在1.5版本之后出現(xiàn)比synchronized更加強大的實現(xiàn)同步鎖的方法,詳情參考使用Lock接口與Condition接口實現(xiàn)生產(chǎn)者與消費者寥闪。


版權(quán)聲明:歡迎轉(zhuǎn)載疲憋,歡迎擴散,但轉(zhuǎn)載時請標明作者以及原文出處柜某,謝謝合作喂击!             ↓↓↓
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淤翔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子监嗜,更是在濱河造成了極大的恐慌抡谐,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刽肠,死亡現(xiàn)場離奇詭異音五,居然都是意外死亡羔沙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門坚嗜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诗充,“玉大人,你說我怎么就攤上這事银室。” “怎么了辜荠?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵抓狭,是天一觀的道長。 經(jīng)常有香客問我否过,道長,這世上最難降的妖魔是什么药磺? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任煤伟,我火速辦了婚禮便锨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘放案。我一直安慰自己吱殉,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布贩虾。 她就那樣靜靜地躺著沥阱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪策精。 梳的紋絲不亂的頭發(fā)上崇棠,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音询刹,去河邊找鬼。 笑死沐兰,一個胖子當著我的面吹牛蔽挠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澳淑,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼杠巡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绑改?” 一聲冷哼從身側(cè)響起兄一,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤出革,失蹤者是張志新(化名)和其女友劉穎渡讼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體展箱,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡蹬昌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年皂贩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婴栽。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡辈末,死狀恐怖映皆,靈堂內(nèi)的尸體忽然破棺而出准脂,到底是詐尸還是另有隱情狸膏,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布贤旷,位于F島的核電站砾脑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏韧衣。R本人自食惡果不足惜畅铭,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望假残。 院中可真熱鬧炉擅,春花似錦、人聲如沸眶俩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗽仪。三九已至柒莉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兢孝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工橘沥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夯秃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓介陶,卻偏偏與公主長得像色建,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子箕戳,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,355評論 3 87
  • Java8張圖 11玻墅、字符串不變性 12走越、equals()方法耻瑟、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • 從三月份找實習(xí)到現(xiàn)在谆构,面了一些公司框都,掛了不少,但最終還是拿到小米魏保、百度、阿里粱哼、京東、新浪檩咱、CVTE揭措、樂視家的研發(fā)崗...
    時芥藍閱讀 42,271評論 11 349
  • 本文出自 Eddy Wiki 胯舷,轉(zhuǎn)載請注明出處:http://eddy.wiki/interview-java.h...
    eddy_wiki閱讀 2,141評論 0 14
  • 行為科學(xué)的研究證明,當我們評價他人绊含,特別是我們的領(lǐng)導(dǎo)時桑嘶,會首先考察以下兩個特質(zhì):他們是否可愛(親和度、和諧度與信任...
    sageness閱讀 92評論 0 0