0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

線程間通信的幾種方式

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-10 16:23 ? 次閱讀

1 使用synchronized,wait,notify,notifyAll

使用synchronized 等方法來控制共享變量,完成交替打印。

思路:

  1. 在同步方法中先判斷信號量,如果不是當(dāng)前需要的信號使用wait()阻塞線程。
  2. 完成打印之后切換信號變量。再喚醒所有線程。
public class ThreadSignaling2 {

    public static void main(String[] args) {
        NorthPrint print = new NorthPrint(new NorthSignal());
        ThreadA threadA = new ThreadA(print);
        ThreadB threadB = new ThreadB(print);
        threadA.start();
        threadB.start();

    }
public static class ThreadA extends Thread {
    private NorthPrint print;
    public ThreadA(NorthPrint print) {
        this.print = print;
    }
    @Override
    public void run() {
        print.printNumber();

    }
}

public static class ThreadB extends Thread {
    private NorthPrint print;
    public ThreadB(NorthPrint print) {
        this.print = print;
    }
    @Override
    public void run() {
        print.printChar();
    }
}
}

public class NorthSignal {
    protected boolean hasDataToProcess = false;
    public synchronized boolean hasDataToProcess(){
        return this.hasDataToProcess;
    }
    public synchronized void setHasDataToProcess(boolean hasData){
        this.hasDataToProcess = hasData;
    }
}

public class NorthPrint {
    private NorthSignal signal;
    public NorthPrint(NorthSignal signal) {
        this.signal = signal;
    }

    public synchronized void printNumber() {
        try {
            for (int i = 1; i <= 26; ) {
                if (signal.hasDataToProcess()) {
                    wait();
                }else {
                    System.out.print(i * 2 - 1);
                    System.out.print(i * 2);
                    signal.setHasDataToProcess(true);
                    i++;
                    notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void printChar() {
        try {
            for (int i = 'A'; i <= 'Z'; ) {
                if (!signal.hasDataToProcess()) {
                    wait();
                }else {
                    System.out.print((char)i);
                    signal.setHasDataToProcess(false);
                    i++;
                    notifyAll();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2 Lock,Condition

通過使用Lock,Condition的signal() 和 await()來進(jìn)行換新阻塞交替打印。

public class ThreadSignalingReentrant {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        new Thread(() - > {
            try{
                lock.lock();
                int i = 1;
                while (i <= 26) {
                    System.out.print(i * 2 - 1);
                    System.out.print(i * 2);
                    i++;
                    condition2.signal();
                    condition1.await();
                }
                condition2.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();

        new Thread(() - > {
            try{
                lock.lock();
                char i = 'A';
                while (i <= 'Z') {
                    System.out.print(i);
                    i++;
                    condition1.signal();
                    condition2.await();
                }
                condition1.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }).start();
    }
}

3 LockSupport

LockSupport 用來創(chuàng)建鎖和其他同步類的基本線程阻塞。當(dāng)調(diào)用LockSupport.park時,表示當(dāng)前線程將會等待,直至獲得許可,當(dāng)調(diào)用LockSupport.unpark時,必須把等待獲得許可的線程作為參數(shù)進(jìn)行傳遞,好讓此線程繼續(xù)運(yùn)行。

其中:

  • park函數(shù),阻塞線程,并且該線程在下列情況發(fā)生之前都會被阻塞: ① 調(diào)用unpark函數(shù),釋放該線程的許可。② 該線程被中斷。③ 設(shè)置的時間到了。并且,當(dāng)time為絕對時間時,isAbsolute為true,否則,isAbsolute為false。當(dāng)time為0時,表示無限等待,直到unpark發(fā)生。
  • unpark函數(shù),釋放線程的許可,即激活調(diào)用park后阻塞的線程。這個函數(shù)不是安全的,調(diào)用這個函數(shù)時要確保線程依舊存活。
public class ThreadSignalingLockSupport {
    private static Thread threadA = null;
    private static Thread threadB = null;
    
    public static void main(String[] args) {
        threadA = new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                System.out.print(i * 2 - 1);
                System.out.print(i * 2);
                i++;
                LockSupport.unpark(threadB);
                LockSupport.park();
            }
        });
        threadB = new Thread(() - > {
            char i = 'A';
            while (i <= 'Z') {
                LockSupport.park();
                System.out.print(i);
                i++;
                LockSupport.unpark(threadA);
            }
        });
        threadA.start();
        threadB.start();
    }
}

4 volatile

根據(jù)volatile修飾的對象在JVM內(nèi)存中的可見性,完成交替打印

public class ThreadSignalingVolatile {

    enum ThreadRunFlag{PRINT_NUM, PRINT_CHAR}
    private volatile static ThreadRunFlag threadRunFlag = ThreadRunFlag.PRINT_NUM;

    public static void main(String[] args) {

        new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                while(threadRunFlag == ThreadRunFlag.PRINT_CHAR){}
                System.out.print(i * 2 - 1);
                System.out.print(i * 2);
                i++;
                threadRunFlag = ThreadRunFlag.PRINT_CHAR;
            }
        }).start();

        new Thread(()- >{
            char i = 'A';
            while (i <= 'Z'){
                while (threadRunFlag == ThreadRunFlag.PRINT_NUM){}
                System.out.print(i);
                i++;
                threadRunFlag = ThreadRunFlag.PRINT_NUM;
            }
        }).start();

    }
}

5 AtomicInteger

同樣利用了AtomicInteger的并發(fā)特性,來完成交替打印。

public class AtomicIntegerSignal {

    private static AtomicInteger threadSignal = new AtomicInteger(1);
    public static void main(String[] args) {

        new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                while(threadSignal.get() == 2){}
                System.out.print(i * 2 - 1);
                System.out.print(i * 2);
                i++;
                threadSignal.set(2);
            }
        }).start();

        new Thread(()- >{
            char i = 'A';
            while (i <= 'Z'){
                while (threadSignal.get() == 1){}
                System.out.print(i);
                i++;
                threadSignal.set(1);
            }
        }).start();

    }
}

6 利用 Piped Stream

使用Stream中的Piped Stream分別控制輸出,但是其運(yùn)行速度極慢。

public class ThreadSignalPipedStream {

    private final PipedInputStream inputStream1;
    private final PipedOutputStream outputStream1;
    private final PipedInputStream inputStream2;
    private final PipedOutputStream outputStream2;
    private final byte[] MSG;

    public ThreadSignalPipedStream() {
        inputStream1 = new PipedInputStream();
        outputStream1 = new PipedOutputStream();
        inputStream2 = new PipedInputStream();
        outputStream2 = new PipedOutputStream();
        MSG = "Go".getBytes();
        try {
            inputStream1.connect(outputStream2);
            inputStream2.connect(outputStream1);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThreadSignalPipedStream signal = new ThreadSignalPipedStream();
        signal.threadA().start();
        signal.threadB().start();

    }

    public Thread threadA (){
        final String[] inputArr = new String[2];

        return new Thread() {
            String[] arr = inputArr;
            PipedInputStream in1 = inputStream1;
            PipedOutputStream out1 = outputStream1;
            @Override
            public void run() {
                int i = 1;
                while (i <= 26) {
                    try {
                        System.out.print(i * 2 - 1);
                        System.out.print(i * 2);
                        out1.write(MSG);
                        byte[] inArr = new byte[2];
                        in1.read(inArr);
                        while(!"Go".equals(new String(inArr))){ }
                        i++;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
    }

    public Thread threadB (){
        final String[] inputArr = new String[2];
        return new Thread() {
            private String[] arr = inputArr;
            private PipedInputStream in2 = inputStream2;
            private PipedOutputStream out2 = outputStream2;
            @Override
            public void run() {
                char i = 'A';
                while (i <= 'Z'){
                    try {
                        byte[] inArr = new byte[2];
                        in2.read(inArr);
                        while(!"Go".equals(new String(inArr))){  }
                        System.out.print(i);
                        i++;
                        out2.write(MSG);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
    }
}

7 利用BlockingQueue

BlockingQueue 通常用于一個線程生產(chǎn)對象,另外一個線程消費(fèi)這些對象的場景。

圖片
img

一個線程負(fù)責(zé)往里面放,另一個線程從里面取一個BlockingQueue。

線程可以持續(xù)將新對象插入到隊(duì)列之中,直到隊(duì)列達(dá)到可容納的臨界點(diǎn)。當(dāng)隊(duì)列到達(dá)臨界點(diǎn)之后,線程生產(chǎn)者會在插入對象是進(jìn)入阻塞狀態(tài),直到有另外一個線程從隊(duì)列中拿走一個對象。消費(fèi)線程會不停的從隊(duì)列中拿出對象。如果消費(fèi)線程從一個空的隊(duì)列中獲取對象的話,那么消費(fèi)線程會處阻塞狀態(tài),直到一個生產(chǎn)線程把對象丟進(jìn)隊(duì)列。

BlockingQueue常用方法如下:

圖片
image-20210906230302480

那么我們使用一個LinkedBlockingQueue來完成開始出現(xiàn)的題目

方法中我們使用offer,peek,poll這幾個方法來完成。

public class ThreadSignalBlockingQueue {
    private static LinkedBlockingQueue< String > queue = new LinkedBlockingQueue<  >();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                System.out.print(i * 2 - 1);
                System.out.print(i * 2);
                i++;
                queue.offer("printChar");
                while(!"printNumber".equals(queue.peek())){}
                queue.poll();
            }
        }).start();

        new Thread(()- >{
            char i = 'A';
            while (i <= 'Z'){
                while(!"printChar".equals(queue.peek())){}
                queue.poll();   
                System.out.print(i);
                i++;
                queue.offer("printNumber");
            }
        }).start();
    }
}

我們也可以使用兩個LinkedBlockinQueue來完成,分別使用帶阻塞的put,take來完成。代碼如下

public class ThreadSignalBlockingQueue2 {
    private static LinkedBlockingQueue< String > queue1 = new LinkedBlockingQueue<  >();
    private static LinkedBlockingQueue< String > queue2 = new LinkedBlockingQueue<  >();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                System.out.print(i * 2 - 1);
                System.out.print(i * 2);
                i++;
                try {
                    queue2.put("printChar");
                    queue1.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();

        new Thread(()- >{
            char i = 'A';
            while (i <= 'Z'){
                try {
                    queue2.take();
                    System.out.print(i);
                    i++;
                    queue1.put("printNumber");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

8 使用CyclicBarrier

CyclicBarrier的字面意思就是可循環(huán)使用的屏障,它可以讓一組線程到達(dá)一個阻塞點(diǎn)(屏障)時被阻塞。直到最后一個線程到達(dá)阻塞點(diǎn)后,屏障才會開門,然后所有被攔截的線程就可以繼續(xù)運(yùn)行。

CyclicBarrier中有一個barrierCommand,主要就是在所有線程到達(dá)阻塞點(diǎn)之后執(zhí)行的一個線程??梢允褂脴?gòu)造方法來 CyclicBarrier(int parties, Runnable barrierAction)進(jìn)行構(gòu)建。

關(guān)于使用CyclicBarrier進(jìn)行交替打印,先來說一下思路。

  1. 利用await()方法使得每循環(huán)一次都阻塞線程。
  2. 將每次循環(huán)輸出的值放到一個共享的同步list里面。
  3. 然后再使用barrierAction到達(dá)阻塞點(diǎn)之后進(jìn)行輸出。由于list里面的值先后順序有變化,所有先排序然后再打印。

下面我們看一下實(shí)操代碼:

public class ThreadSignalCyclicBarrier {
    private static List< String > list =  Collections.synchronizedList(new ArrayList<  >());
    public static void main(String[] args) throws Exception {
        CyclicBarrier barrier = new CyclicBarrier(2,barrierRun());

        new Thread(() - > {
            int i = 1;
            while (i <= 26) {
                list.add(String.valueOf(i * 2 - 1));
                list.add(String.valueOf(i * 2));
                i++;
                try {
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }).start();

        new Thread(()- >{
            char i = 'A';
            while (i <= 'Z'){
                try {
                    list.add(String.valueOf(i));
                    i++;
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();


    }

    public static Runnable barrierRun(){
        return new Runnable() {
            @Override
            public void run() {
                Collections.sort(list);
                list.forEach(str- >System.out.print(str));
                list.clear();
            }
        };
    }
}
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 通信
    +關(guān)注

    關(guān)注

    18

    文章

    5952

    瀏覽量

    135800
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4286

    瀏覽量

    62337
  • Lock
    +關(guān)注

    關(guān)注

    0

    文章

    10

    瀏覽量

    7756
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    19636
收藏 人收藏

    評論

    相關(guān)推薦

    Linux多線程線程同步

    進(jìn)程所花費(fèi)的空間,而且,線程彼此切換所需的時間也遠(yuǎn)遠(yuǎn)小于進(jìn)程切換所需要的時間。 線程方便的通信
    發(fā)表于 12-08 14:14

    線程的同步方式有哪幾種?

    線程的同步方式有哪幾種?
    發(fā)表于 05-26 07:13

    IOT-OS之RT-Thread--- 線程同步與線程通信

    rt_thread,下面要介紹線程的同步與通信,線程同步對象rt_sem / rt_mutex / rt_event和
    發(fā)表于 07-02 06:15

    哪些方式可以實(shí)現(xiàn)Linux系統(tǒng)下的進(jìn)程通信

    哪些方式可以實(shí)現(xiàn)Linux系統(tǒng)下的進(jìn)程通信?進(jìn)程與線程有哪些不同之處呢?
    發(fā)表于 12-24 06:38

    RTT多線程通信機(jī)制有哪幾種及推薦?

    針對采用RTT OS ,啟動了4個線程,兩個串口讀寫線程(數(shù)據(jù)>10byte以上) 一個觸摸按鍵線程 一個顯示線程,針對這幾個線程間數(shù)據(jù)傳輸
    發(fā)表于 04-07 15:52

    QNX消息傳遞及其在線程通信的應(yīng)用

    本文介紹了QNX 嵌入式實(shí)時多任務(wù)操作系統(tǒng)的消息傳遞和微內(nèi)核體系結(jié)構(gòu)的特點(diǎn),創(chuàng)建線程的方法,消息傳遞的基本原理,以及阻塞式消息傳遞在線程通信的實(shí)現(xiàn)方法,并給出了
    發(fā)表于 08-11 08:46 ?31次下載

    c線程通信

    對于學(xué)習(xí)嵌入式Linux開發(fā)得朋友說,這篇文章幫助你更加了解線程通信
    發(fā)表于 08-09 15:12 ?3次下載

    線程和進(jìn)程的區(qū)別和聯(lián)系,線程和進(jìn)程通信方式

    摘要:進(jìn)程和線程都是計(jì)算里的兩項(xiàng)執(zhí)行活動,各有特色和優(yōu)勢。下面就來介紹線程和進(jìn)程之間的區(qū)別聯(lián)系以及通信方式
    發(fā)表于 12-08 14:12 ?1.2w次閱讀

    進(jìn)程線程通信方式

    進(jìn)程通信則不同,它的數(shù)據(jù)空間的獨(dú)立性決定了它的通信相對比較復(fù)雜,需要通過操作系統(tǒng)。以前進(jìn)程通信只能是單機(jī)版的,現(xiàn)在操作系統(tǒng)都繼承了基
    的頭像 發(fā)表于 04-09 15:58 ?8885次閱讀
    進(jìn)程<b class='flag-5'>間</b>與<b class='flag-5'>線程</b><b class='flag-5'>間</b>的<b class='flag-5'>通信</b><b class='flag-5'>方式</b>

    淺析嵌入式Linux中進(jìn)程幾種通信方式

    線程通信:由于多線程共享地址空間和數(shù)據(jù)空間,所以多個線程
    的頭像 發(fā)表于 08-20 09:03 ?6389次閱讀

    了解Linux多線程線程同步

    進(jìn)程通信IPC,線程可以直接讀寫進(jìn)程數(shù)據(jù)段(如全局變量)來進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
    發(fā)表于 04-23 14:23 ?708次閱讀
    了解Linux多<b class='flag-5'>線程</b>及<b class='flag-5'>線程</b><b class='flag-5'>間</b>同步

    使用MQTT作為進(jìn)程通信方式

    小編對Linux這一塊的實(shí)際開發(fā)經(jīng)驗(yàn)雖然還不是很足,但也知道進(jìn)程通信有那么幾種方式:管道、消息隊(duì)列、共享內(nèi)存、套接字等。
    的頭像 發(fā)表于 10-22 12:09 ?6040次閱讀
    使用MQTT作為進(jìn)程<b class='flag-5'>間</b><b class='flag-5'>通信</b>的<b class='flag-5'>方式</b>

    RT-Thread文檔_線程通信

    RT-Thread文檔_線程通信
    發(fā)表于 02-22 18:29 ?0次下載
    RT-Thread文檔_<b class='flag-5'>線程</b><b class='flag-5'>間</b><b class='flag-5'>通信</b>

    線程池的創(chuàng)建方式幾種

    的開銷。線程池的創(chuàng)建方式有多種,下面將詳細(xì)介紹幾種常用的線程池創(chuàng)建方式。 手動創(chuàng)建線程池 手動創(chuàng)
    的頭像 發(fā)表于 12-04 16:52 ?806次閱讀

    java實(shí)現(xiàn)多線程幾種方式

    Java實(shí)現(xiàn)多線程幾種方式線程是指程序中包含了兩個或以上的線程,每個線程都可以并行執(zhí)行不同
    的頭像 發(fā)表于 03-14 16:55 ?552次閱讀