3.線程間通信

軟件都是由不同的模塊組成一個系統(tǒng)啦粹,從而模塊與模塊間的通信,線程與線程間通信是經(jīng)常碰到的。下面我們介紹了關于線程間通信的幾種技術方案

1.wait/notify

1.1不使用wait/notify的代碼演示

public class TpadTask {
    private List<Integer> list = new ArrayList<>();

    public void doAdd() {
        Integer num = new Random().nextInt(100);
        list.add(num);
    }

    public int size() {
        return list.size();
    }
}
public class ThreadA extends Thread {
    private TpadTask task;

    public ThreadA(TpadTask task) {
        super();
        this.task = task;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            task.doAdd();
            System.out.println(i);
        }
    }
}
public class ThreadB extends Thread {
    private TpadTask task;

    public ThreadB(TpadTask task) {
        super();
        this.task = task;
    }

    @Override
    public void run() {
        try {
            while (true) {
                if (task.size() == 5) {
                    System.out.println("已達到5,b線程要退出");
                    throw new InterruptedException();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
public class Client {
    public static void main(String[] args) {
        TpadTask task = new TpadTask();
        ThreadA a = new ThreadA(task);
        ThreadB b = new ThreadB(task);
        a.start();
        b.start();
    }
}

說明:線程A是進行對list進行添加數(shù)據(jù),線程B是對list的大小進行監(jiān)控赋访,如果達到5,則主動退出缓待,拋出異常蚓耽。

  • 結(jié)果
0
1
2
3
4
已達到5,b線程要退出
5
6
7
8
9
java.lang.InterruptedException
    at communication.ThreadB.run(ThreadB.java:21)

缺點:

  • 線程B需要不停的循環(huán)來判斷定條件旋炒,這樣會浪費CPU資源步悠。

1.2 wait/notify機制

  • 1.wait()

wait()的作用是使當前執(zhí)行的線程進行等待,wait()方法是Object類的方法。該方法是將當前線程置入預執(zhí)行隊列瘫镇,并在wait代碼處停止執(zhí)行鼎兽,直到接收到通知或被中斷執(zhí)行答姥。

在調(diào)用wait()之前,線程必須獲取該對象的對象級別鎖谚咬,即只能在同步方法或者同步代碼塊中調(diào)用wait()方法鹦付。

在執(zhí)行wait()方法后,當前線程釋放鎖序宦。從wait()返回前睁壁,線程與其他線程競爭重新獲取鎖。如果調(diào)用wait()沒有持有鎖互捌,則會拋出異常潘明。

  • wait方法的注釋
The current thread must own this object's monitor.
  • 2.notify()

notify()的作用是使停止的線程繼續(xù)運行。

在調(diào)用notify()之前秕噪,線程必須獲取該對象的對象級別鎖钳降,也只能在同步方法或者同步代碼塊調(diào)用

執(zhí)行notify()方法后,當前線程不會馬上釋放該對象鎖腌巾。呈現(xiàn)wait的線程也不會馬上獲取到該對象鎖遂填。要等到執(zhí)行notify()方法的線程將程序執(zhí)行完。也就是退出synchronized代碼塊后澈蝙,當前線程才會釋放該對象鎖吓坚。

  • 3.改造上述代碼

  • TpadTask代碼不變

  • ThreadA代碼改造

public class ThreadA extends Thread {
    private TpadTask task;

    private Object lock;

    public ThreadA(TpadTask task, Object obj) {
        super();
        this.task = task;
        this.lock = obj;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                synchronized (lock) {
                    task.doAdd();
                    System.out.println(i);
                    if (task.size() == 5) {
                        lock.wait();
                        System.out.println("接收到信息!5朴礁击!");
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • ThreadB
public class ThreadB extends Thread {
    private TpadTask task;

    private Object lock;

    public ThreadB(TpadTask task, Object obj) {
        super();
        this.task = task;
        this.lock = obj;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (task.size() == 5) {
                   lock.notify();
                   System.out.println("已發(fā)出信號!6涸亍哆窿!");
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
  • 測試
public class Client {
    public static void main(String[] args) throws InterruptedException {
        TpadTask task = new TpadTask();
        Object lock = new Object();
        ThreadA a = new ThreadA(task, lock);
        a.start();
        ThreadB b = new ThreadB(task, lock);
        b.start();
    }
}
  • 結(jié)果
0
1
2
3
4
已發(fā)出信號!@髡濉挚躯!
接收到信息!2粱唷码荔!
5
6
7
8
9
  • 4.線程狀態(tài)切換
線程狀態(tài)切換
  • 5.wait(long timeout)

等待一段時間,看是否有線程對鎖進行喚醒感挥。如果超過這個時間缩搅,則自動喚醒。

2.通過管道流進行通信(pipeStream)

jdk提供了4個類用于管道流通信链快。java.io.PipedInputStream誉己、java.io.PipedOutputStream眉尸、java.io.PipedReader域蜗、java.io.PipedWriter

它們的作用是讓多線程可以通過管道進行線程間的通訊巨双。在使用管道通信時,必須將PipedOutputStream和PipedInputStream配套使用霉祸。

大致的流程是:我們在線程A中向PipedOutputStream中寫入數(shù)據(jù)筑累,這些數(shù)據(jù)會自動的發(fā)送到與PipedOutputStream對應的PipedInputStream中,進而存儲在PipedInputStream的緩沖中丝蹭;此時慢宗,線程B通過讀取PipedInputStream中的數(shù)據(jù)。就可以實現(xiàn)奔穿,線程A和線程B的通信镜沽。

  • PipedOutputStream、PipedInputStream代碼演示

  • 新建生產(chǎn)消息線程

public class Producer  extends Thread {
    private PipedOutputStream pos;

    public Producer(PipedOutputStream pos) {
        this.pos = pos;
    }

    @Override
    public void run() {
        super.run();
        try {
            pos.write("Hello".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 新建接收消息線程
public class Consumer extends Thread  {
    private PipedInputStream pis;

    public Consumer(PipedInputStream pis) {
        this.pis = pis;
    }

    @Override
    public void run() {
        super.run();
        byte[] b = new byte[100]; // 將數(shù)據(jù)保存在byte數(shù)組中
        try {
            int len = pis.read(b); // 從數(shù)組中得到實際大小贱田。
            System.out.println(String.format("接收到信號:%s",new String(b, 0, len)));
            pis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 測試
public class Client {
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedOutputStream pos = new PipedOutputStream();
        PipedInputStream pis = new PipedInputStream();
        try {
            pos.connect(pis);// 連接管道

            new Producer(pos).start();// 啟動線程

            new Consumer(pis).start();// 啟動線程

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 結(jié)果
接收到信號:Hello

pos.connect(pis)作用是將“管道輸入流”和“管道輸出流”關聯(lián)起來缅茉。查看PipedWriter.java和PipedReader.java中connect()的源碼;我們知道 out.connect(in); 等價于 in.connect(out)男摧。當然JDK還支持管道通信字符流蔬墩。

3.Join

主線程創(chuàng)建并啟動子線程,如果子線程需要消耗大量的資源耗拓,主線程往往早于子線程執(zhí)行完畢拇颅。而主線程需要等待子線程的結(jié)果,這時候使用join方法乔询。

  • 例子
public class CustomerThread extends Thread {
    public CustomerThread(){
        super();
    }

    @Override
    public void run() {
        System.out.println("我是子線程代碼");
    }
}
public class Client {
    public static void main(String[] args) throws InterruptedException {
        CustomerThread a=new CustomerThread();
        a.start();
        System.out.println("我是主線程代碼");
    }
}
  • 輸出
我是主線程代碼
我是子線程代碼

主線程已經(jīng)執(zhí)行完畢了樟插,才執(zhí)行子線程,如果需要用到子線程的結(jié)果哥谷。那么只需要加一句join()岸夯。

public class Client {
    public static void main(String[] args) throws InterruptedException {
        CustomerThread a=new CustomerThread();
        a.start();
        a.join();//join語句
        System.out.println("我是主線程代碼");
    }
}
  • 結(jié)果
我是子線程代碼
我是主線程代碼

join的方法注釋是:

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 */
 
while (isAlive()) {
    wait(0);
}

使得所屬的線程對象正常的執(zhí)行,使得當前的線程進行無線等待们妥,直到所屬線程銷毀后猜扮,再執(zhí)行當前線程。

在join過程中监婶,如果當前線程對象被中斷旅赢,則當前線程對象拋出異常。

join(long)中的參數(shù)是等待的時間惑惶。例如join(2000)煮盼,只等等2秒子線程的執(zhí)行時間。超過2秒自動執(zhí)行主線程代碼带污。

3.1join(long)與sleep(long)的區(qū)別

join內(nèi)部使用的是wait(long)來實現(xiàn)的僵控。所以join()具有釋放鎖的特性。當前線程的鎖被釋放鱼冀,那么其他線程就可以調(diào)用此線程的同步方法了报破。

Thread.sleep(long)方法卻不釋放鎖悠就。

4.ThreadLocal

ThreadLocal并不是一個Thread,而是Thread的局部變量充易,也許把它命名為ThreadLocalVariable更容易讓人理解一些梗脾。

ThreadLocal是解決每個線程綁定屬于自己的值。

ThreadLocal是解決線程安全問題一個很好的思路盹靴,它通過為每個線程提供一個獨立的變量副本炸茧。解決了變量并發(fā)訪問的沖突問題。在很多情況下稿静,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單梭冠,更方便,且結(jié)果程序擁有更高的并發(fā)性改备。

  • 代碼演示

  • 新建ThreadLocal公用類

public class ThreadLocalVariable {
    public static ThreadLocal threadLocalVariable = new ThreadLocal();
}
  • 新建ThreadA
public class ThreadA extends Thread {
    @Override
    public void run() {
        try {
            for(int i=0;i<5;i++){
                ThreadLocalVariable.threadLocalVariable.set(String.format("ThreadA:%s",i));
                System.out.println(ThreadLocalVariable.threadLocalVariable.get());
            }
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
  • 新建ThreadB
public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            for(int i=0;i<5;i++){
                ThreadLocalVariable.threadLocalVariable.set(String.format("ThreadB:%s",i));
                System.out.println(ThreadLocalVariable.threadLocalVariable.get());
            }
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
  • Client
public class Client {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        ThreadB b = new ThreadB();
        a.start();
        b.start();
        ThreadLocalVariable.threadLocalVariable.set("a");
        System.out.println(ThreadLocalVariable.threadLocalVariable.get());

    }
}
  • 結(jié)果
a
ThreadA:0
ThreadA:1
ThreadA:2
ThreadA:3
ThreadA:4
ThreadB:0
ThreadB:1
ThreadB:2
ThreadB:3
ThreadB:4

每個線程都對threadLocalVariable進行設置值妈嘹,但是取值的時候,都是取的各自的設置的值绍妨。

沒賦值前返回的值是null

protected T initialValue() {
    return null;
}

4.1InheritableThreadLocal

該類可以讓子線程從父線程中取出值润脸。而子線程從父類繼承的值可以在子線程進行對值得修改

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市他去,隨后出現(xiàn)的幾起案子毙驯,更是在濱河造成了極大的恐慌,老刑警劉巖灾测,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爆价,死亡現(xiàn)場離奇詭異,居然都是意外死亡媳搪,警方通過查閱死者的電腦和手機铭段,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秦爆,“玉大人序愚,你說我怎么就攤上這事〉认蓿” “怎么了爸吮?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長望门。 經(jīng)常有香客問我形娇,道長,這世上最難降的妖魔是什么筹误? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任桐早,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哄酝。我一直安慰自己所灸,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布炫七。 她就那樣靜靜地躺著,像睡著了一般钾唬。 火紅的嫁衣襯著肌膚如雪万哪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天抡秆,我揣著相機與錄音奕巍,去河邊找鬼。 笑死儒士,一個胖子當著我的面吹牛的止,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播着撩,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼诅福,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拖叙?” 一聲冷哼從身側(cè)響起氓润,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薯鳍,沒想到半個月后咖气,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡挖滤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年崩溪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斩松。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡伶唯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惧盹,到底是詐尸還是另有隱情抵怎,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布岭参,位于F島的核電站反惕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏演侯。R本人自食惡果不足惜姿染,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悬赏,春花似錦狡汉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至兵多,卻和暖如春尖啡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剩膘。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工衅斩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠褐。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓畏梆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奈懒。 傳聞我的和親對象是個殘疾皇子奠涌,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

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