【多線程】Java線程間是如何通信的呢?


線程開始運行,擁有自己的棧空間,那多個線程如何相互配合完成工作,這就涉及到了線程間的通信。
線程通信是使線程間能夠互相發送信號,是使線程能夠等待其他線程的信號。比如線程 A 在執行到某個條件通知線程 B 執行某個操作

一、共享內存機制

(1)同步–synchronized

線程同步是線程之間按照⼀定的順序執⾏,可以使⽤鎖來實現達到線程同步,也就是在需要同步的代碼塊里加上關鍵字synchronized 。因爲⼀個鎖同⼀時間只能被⼀個線程持有。

關鍵字synchronized可以修飾方法或者以同步塊,它主要確保多個線程在同一個時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性。

這種方式,線程需要不斷地去嘗試獲得鎖,如果失敗了,再繼續嘗試。這可能會耗費服務器資源。

基於synchronized實現線程A和線程B通信
線程A執行完,再讓線程B執行,使用對象鎖實現

public class ObjectLock {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadA " + i);
                }
            }
        }
    }

    static class ThreadB implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadB " + i);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()) .start();
        Thread.sleep(10);// 這裏讓線程睡10毫秒,可確保A先獲得鎖
        System.out.println("---------------------");
        new Thread(new ThreadB()).start();
    }
}

運行結果:
在這裏插入圖片描述

線程A和線程B需要訪問同一個對象lock,誰獲得鎖,誰就先執行,這裏控制的是讓線程A先執行(Thread.sleep(10)爲的就是A先獲得鎖)。線程B要等線程A執行完再執行,所以是同步的,這就實現了線程間的通信

(2)信號量 --volatile

Java支持多個線程同時訪問一個對象或者對象的成員變量,由於每個線程可以擁有這個變量的拷貝,所以程序在執行過程中,一個線程看到的變量並不一定是最新的。
關鍵字volatile可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內存中獲取,而對它的改變必須同步刷新回共享內存,它能保證所有線程對變量訪問的可見性。

volitile關鍵字能夠保證內存的可⻅性,如果⽤volitile關鍵字聲明瞭⼀個變量,在⼀個線程⾥⾯改變了這個變量的值,那其它線程是⽴⻢可⻅更改後的值的。

基於volatile關鍵字實現線程A和線程B的通信
線程A輸出0,然後線程B輸出1,再然後線程A輸出2…

public class Signal {
    private static volatile int signal = 0;
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 0) {
                    System.out.println("threadA: " + signal);
                    synchronized (this) {
                        signal++;
                    }
                }
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    synchronized (this) {
                        signal = signal + 1;
                    }
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

運行結果
在這裏插入圖片描述

volatile 變量需要進⾏原⼦操作。 signal++ 並不是⼀個原⼦操
作,所以我們需要使⽤ synchronized 給它“上鎖”

關於synchronized與volatile ,synchronized主要做的是多線程順序執行,也就是同一個時間只有一個線程在執行,線程A執行完了再讓線程B執行,volatile主要做的是讓多線程間共享的變量保證一致,也就是線程A對變量操作了,線程B對變量操作時是知道線程A對變量的操作的,是在線程A操作後的變量上進行操作。

二、等待/通知機制(wait/notify)

一個線程修改了一個對象的值,而另一個線程感知到了變化,然後進行相應的操作,整個過程開始於一個線程,而最終執行又是另一個線程
等待/通知機制使⽤的是使⽤同⼀個對象鎖,如果你兩個線程使
⽤的是不同的對象鎖,那它們之間是不能⽤等待/通知機制通信的。

等待/通知的相關方法

方法名稱 含義
notify() 通知一個對象上等待的線程,使其從wait()方法返回,而返回的前提是該線程獲取到了對象的鎖
notifyAll() 通知所有等待在該對象上的線程
wait() 調用該方法的線程進入WAITING狀態,只有等待另外線程的通知或被中斷纔會返回,調用wait()方法後,會釋放對象的鎖
wait(long) 超時等待一段時間,沒有通知就超時返回,參數時間是毫秒
wait(long,int) 對於超時時間更細粒度的控制,可達到納秒

notify()和notifyAll()的區別:
notify()⽅法會隨機叫醒⼀個正在等待的線程,⽽notifyAll()會叫醒所有正在等待的線程。

⼀個鎖同⼀時刻只能被⼀個線程持有,lock.wait() 讓⾃⼰進⼊等待狀態,會釋放鎖
lock.notify() 叫醒⼀個正在等待的線程,但並沒有釋放鎖 lock。

基於Object 類的 wait() ⽅法和 notify() ⽅法實現
線程A執行完,線程B執行,再線程A執行…

public class WaitNotify {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadA: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadB: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

運行結果
在這裏插入圖片描述

三、管道

管道是基於“管道流”的通信⽅式,管道輸入/輸出流要用於線程之間的數據傳輸,而傳輸的媒介爲內存。
管道輸入/輸出流的體現:
基於字符的:PipedWriter 、 PipedReader 、
基於字節流的:PipedOutputStream 、 PipedInputStream

使⽤管道多半與I/O流相關。當我們⼀個線程需要先另⼀個線程發送⼀個信息(⽐如字符串)或者⽂件等等時,就需要使⽤管道通信了

像消息傳遞機制,通過管道,將一個線程中的消息發送給另一個

基於PipedWriter 和PipedReader 的實現的ReaderThread 和WriterThread 的通信
WriterThread 寫了內容,ReaderThread 讀到並打印

public class Piped {
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedWriter writer = new PipedWriter();
        PipedReader reader = new PipedReader();
        writer.connect(reader);
        new Thread(new ReaderThread(reader)).start();
        Thread.sleep(10);
        new Thread(new WriterThread(writer)).start();
    }
    static class ReaderThread implements Runnable{
        private PipedReader in;
        public ReaderThread(PipedReader in){
            this.in=in;
        }
        @Override
        public void run() {
            System.out.println("this is a reader");
            int receice=0;
            try {
                while ((receice=in.read())!=-1){
                    System.out.println("read "+(char) receice);
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }

        }
    }

    static class WriterThread implements Runnable{
        private PipedWriter out;
        public WriterThread(PipedWriter out){
            this.out=out;
        }
        @Override
        public void run() {
            System.out.println("this is a writer");
            try {
                out.write("write A");
            }catch (IOException ex){
                ex.printStackTrace();
            }finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

運行結果
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章