欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入理解Java中線程間的通信

 更新時間:2022年11月25日 08:17:10   作者:初念初戀  
一般來講,線程內(nèi)部有自己私有的線程上下文,互不干擾。但是當(dāng)我們需要多個線程之間相互協(xié)作的時候,就需要我們掌握J(rèn)ava線程的通信方式。本文將介紹Java線程之間的幾種通信原理,需要的可以參考一下

合理的使用Java多線程可以更好地利用服務(wù)器資源。一般來講,線程內(nèi)部有自己私有的線程上下文,互不干擾。但是當(dāng)我們需要多個線程之間相互協(xié)作的時候,就需要我們掌握J(rèn)ava線程的通信方式。本文將介紹Java線程之間的幾種通信原理。

鎖與同步

在Java中,鎖的概念都是基于對象的,所以我們又經(jīng)常稱它為對象鎖。一個鎖同一時間只能被一個線程持有。也就是說,一個鎖如果被一個線程所持有,那其他線程如果需要得到這個鎖,就得等這個線程釋放該鎖。

線程之間,有一個同步的概念。在多線程中,可能有多個線程試圖訪問一個有限的資源,必須預(yù)防這種情況的發(fā)生。

所以引入了同步機制:在線程使用一個資源時為其加鎖,這樣其他的線程便不能訪問那個資源了,直到解鎖后才可以訪問。線程同步是線程之間按照一定的順序執(zhí)行。

我們先來看看一個無鎖的程序:

public class NoLock {

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread A " + i);
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread B " + i);
            }
        }
    }

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

執(zhí)行這個程序,你會在控制臺看到,線程A和線程B各自獨立工作,輸出自己的打印值。每一次運行結(jié)果都會不一樣。如下是我的電腦上某一次運行的結(jié)果。

...
Thread B 74
Thread B 75
Thread B 76
Thread B 77
Thread B 78
Thread B 79
Thread B 80
Thread A 3
Thread A 4
Thread A 5
Thread A 6
Thread A 7
Thread A 8
Thread A 9
...

現(xiàn)在有一個需求,想等A先執(zhí)行完之后,再由B去執(zhí)行,怎么辦呢?最簡單的方式就是使用一個“對象鎖”。

public class ObjLock {

    private static Object lock = new Object();

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread A " + i);
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread B " + i);
                }
            }
        }
    }

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

這里聲明了一個名字為lock的對象鎖。我們在ThreadAThreadB內(nèi)需要同步的代碼塊里,都是用synchronized關(guān)鍵字加上了同一個對象鎖lock。

上文我們說到了,根據(jù)線程和鎖的關(guān)系,同一時間只有一個線程持有一個鎖,那么線程B就會等線程A執(zhí)行完成后釋放lock,線程B才能獲得鎖lock。

這里在主線程里使用sleep方法睡眠了10毫秒,是為了防止線程B先得到鎖。因為如果同時start,線程A和線程B都是出于就緒狀態(tài),操作系統(tǒng)可能會先讓B運行。這樣就會先輸出B的內(nèi)容,然后B執(zhí)行完成之后自動釋放鎖,線程A再執(zhí)行。

等待/通知機制

上面一種基于“鎖”的方式,線程需要不斷地去嘗試獲得鎖,如果失敗了,再繼續(xù)嘗試。這可能會耗費服務(wù)器資源。 而等待/通知機制是另一種方式。

Java多線程的等待/通知機制是基于Object類的wait()方法和notify()notifyAll()方法來實現(xiàn)的。

notify()方法會隨機叫醒一個正在等待的線程,而notifyAll()會叫醒所有正在等待的線程。

前面我們講到,一個鎖同一時刻只能被一個線程持有。而假如線程A現(xiàn)在持有了一個鎖lock并開始執(zhí)行,它可以使用lock.wait()讓自己進入等待狀態(tài)。這個時候,lock這個鎖是被釋放了的。

這時,線程B獲得了lock這個鎖并開始執(zhí)行,它可以在某一時刻,使用lock.notify(),通知之前持有lock鎖并進入等待狀態(tài)的線程A,說“線程A你不用等了,可以往下執(zhí)行了”。

需要注意的是,這個時候線程B并沒有釋放鎖lock,除非線程B這個時候使用lock.wait()釋放鎖,或者線程B執(zhí)行結(jié)束自行釋放鎖,線程A才能得到lock鎖。

用代碼來實現(xiàn)一下:

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(1000);
        new Thread(new ThreadB()).start();
    }
}

// 輸出:
ThreadA: 0
ThreadB: 0
ThreadA: 1
ThreadB: 1
ThreadA: 2
ThreadB: 2
ThreadA: 3
ThreadB: 3
ThreadA: 4
ThreadB: 4

線程A和線程B首先打印出自己需要的東西,然后使用notify()方法叫醒另一個正在等待的線程,然后自己使用wait()方法陷入等待并釋放lock鎖。

需要注意的是等待/通知機制使用的是使用同一個對象鎖,如果你兩個線程使用的是不同的對象鎖,那它們之間是不能用等待/通知機制通信的。

信號量--Volatile

信號量(Semaphore) :有時被稱為信號燈,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來保證兩個或多個關(guān)鍵代碼段不被并發(fā)調(diào)用。在進入一個關(guān)鍵代碼段之前,線程必須獲取一個信號量;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關(guān)鍵代碼段的線程必須等待直到第一個線程釋放信號量。

本文不是要介紹這個類,而是介紹一種基于volatile關(guān)鍵字的自己實現(xiàn)的信號量通信。

volatile關(guān)鍵字能夠保證內(nèi)存的可見性,如果用volatile關(guān)鍵字聲明了一個變量,在一個線程里面改變了這個變量的值,那其它線程是立馬可見更改后的值的。

比如我現(xiàn)在有一個需求,我想讓線程A輸出0,然后線程B輸出1,再然后線程A輸出2…以此類推。我應(yīng)該怎樣實現(xiàn)呢?

public class Count {
    private static volatile int count = 0;

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (count < 5) {
                if (count % 2 == 0) {
                    System.out.println("threadA: " + count);
                    synchronized (this) {
                        count++;
                    }
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (count < 5) {
                if (count % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    synchronized (this) {
                        count++;
                    }
                }
            }
        }
    }

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

// 輸出:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
threadA: 4

我們可以看到,使用了一個volatile變量count來實現(xiàn)了“信號量”的模型。這里需要注意的是,volatile變量需要進行原子操作,而count++并不是一個原子操作,根據(jù)需要使用synchronized給它“上鎖”,或者是使用AtomicInteger等原子類。

信號量的應(yīng)用場景

假如在一個停車場中,車位是我們的公共資源,線程就如同車輛,而看門的管理員就是起的“信號量”的作用。 因為在這種場景下,多個線程需要相互合作,我們用簡單的“鎖”和“等待通知機制”就不那么方便了。這個時候就可以用到信號量。

管道輸入/輸出流

管道是基于“管道流”的通信方式。JDK提供了PipedWriter、 PipedReader、 PipedOutputStream、 PipedInputStream

其中,前面兩個是基于字符的,后面兩個是基于字節(jié)流的。

以下示例代碼使用的是基于字符的:

public class Pipe {
    static class ReaderThread implements Runnable {
        private PipedReader reader;

        public ReaderThread(PipedReader reader) {
            this.reader = reader;
        }

        @Override
        public void run() {
            System.out.println("this is reader");
            int receive = 0;
            try {
                while ((receive = reader.read()) != -1) {
                    System.out.print((char)receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    static class WriterThread implements Runnable {

        private PipedWriter writer;

        public WriterThread(PipedWriter writer) {
            this.writer = writer;
        }

        @Override
        public void run() {
            System.out.println("this is writer");
            int receive = 0;
            try {
                writer.write("test");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    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(1000);
        new Thread(new WriterThread(writer)).start();
    }
}

// 輸出:
this is reader
this is writer
test

我們通過線程的構(gòu)造函數(shù),傳入了PipedWritePipedReader對象??梢院唵畏治鲆幌逻@個示例代碼的執(zhí)行流程:

  • 線程ReaderThread開始執(zhí)行,
  • 線程ReaderThread使用管道reader.read()進入”阻塞“,
  • 線程WriterThread開始執(zhí)行,
  • 線程WriterThread用writer.write("test")往管道寫入字符串,
  • 線程WriterThread使用writer.close()結(jié)束管道寫入,并執(zhí)行完畢,
  • 線程ReaderThread接受到管道輸出的字符串并打印,
  • 線程ReaderThread執(zhí)行完畢。

管道通信的應(yīng)用場景

這個很好理解。使用管道多半與I/O流相關(guān)。當(dāng)我們一個線程需要先另一個線程發(fā)送一個信息(比如字符串)或者文件等等時,就需要使用管道通信了。

Thread.join()方法

join()方法是Thread類的一個實例方法。它的作用是讓當(dāng)前線程陷入“等待”狀態(tài),等join的這個線程執(zhí)行完成后,再繼續(xù)執(zhí)行當(dāng)前線程。

有時候,主線程創(chuàng)建并啟動了子線程,如果子線程中需要進行大量的耗時運算,主線程往往將早于子線程結(jié)束之前結(jié)束。

如果主線程想等待子線程執(zhí)行完畢后,獲得子線程中的處理完的某個數(shù)據(jù),就要用到j(luò)oin方法了。

示例代碼:

public class Join {
    static class ThreadA implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("我是子線程,我先睡一秒");
                Thread.sleep(1000);
                System.out.println("我是子線程,我睡完了一秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadA());
        thread.start();
        thread.join();
        System.out.println("如果不加join方法,我會先被打出來,加了就不一樣了");
    }
}

ThreadLocal類

ThreadLocal是一個本地線程副本變量工具類。內(nèi)部是一個弱引用的Map來維護。

嚴(yán)格來說,ThreadLocal類并不屬于多線程間的通信,而是讓每個線程有自己”獨立“的變量,線程之間互不影響。它為每個線程都創(chuàng)建一個副本,每個線程可以訪問自己內(nèi)部的副本變量。

ThreadLocal類最常用的就是set方法和get方法。示例代碼:

public class Profiler {

    private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() {
        protected Long initialValue() {
            return System.currentTimeMillis();
        }
    };

    public static void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }

    public static void main(String[] args) throws InterruptedException {
        Profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("耗時:" + Profiler.end() + "mills");
    }
}

// 輸出:
耗時:1001mills

Profiler 可以被復(fù)用在方法的耗時統(tǒng)計的功能上,在方法的入口前執(zhí)行begin()方法,在方法調(diào)用后執(zhí)行end()方法,好處是兩個方法的調(diào)用不用在一個方法和類中,比如在AOP(面向切面編程)中,可以在方法調(diào)用前的切入點執(zhí)行begin()方法,而在方法調(diào)用切入點執(zhí)行end()方法,這樣依舊可以獲得方法的執(zhí)行耗時。

ThreadLocal的應(yīng)用場景

最常見的ThreadLocal使用場景為用來解決數(shù)據(jù)庫連接、Session管理等。數(shù)據(jù)庫連接和Session管理涉及多個復(fù)雜對象的初始化和關(guān)閉。如果在每個線程中聲明一些私有變量來進行操作,那這個線程就變得不那么“輕量”了,需要頻繁的創(chuàng)建和關(guān)閉連接。

小結(jié)

線程間通信使線程成為一個整體,提高系統(tǒng)之間的交互性,在提高CPU利用率的同時可以對線程任務(wù)進行有效的把控與監(jiān)督。

以上就是深入理解Java中線程間的通信的詳細(xì)內(nèi)容,更多關(guān)于Java線程通信的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • spring security結(jié)合jwt實現(xiàn)用戶重復(fù)登錄處理

    spring security結(jié)合jwt實現(xiàn)用戶重復(fù)登錄處理

    本文主要介紹了spring security結(jié)合jwt實現(xiàn)用戶重復(fù)登錄處理,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Java通過JavaMail發(fā)送郵件功能

    Java通過JavaMail發(fā)送郵件功能

    這篇文章主要為大家詳細(xì)介紹了Java通過JavaMail發(fā)送郵件功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • 詳細(xì)總結(jié)Java創(chuàng)建文件夾的方法及優(yōu)缺點

    詳細(xì)總結(jié)Java創(chuàng)建文件夾的方法及優(yōu)缺點

    很多小伙伴都不知道如何用Java創(chuàng)建文件夾,今天給大家整理了這篇文章,文中有非常詳細(xì)的方法介紹及方法的優(yōu)缺點,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • 加速spring/springboot應(yīng)用啟動速度詳解

    加速spring/springboot應(yīng)用啟動速度詳解

    這篇文章主要介紹了加速spring/springboot應(yīng)用啟動速度詳解,本文通過實例代碼給大家介紹的非常詳細(xì)對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • java虛擬機創(chuàng)建失敗的原因整理

    java虛擬機創(chuàng)建失敗的原因整理

    在本篇文章里小編給大家整理了關(guān)于創(chuàng)建java虛擬機失敗的解決方法和知識點,需要的朋友們可以參考學(xué)習(xí)下。
    2020-02-02
  • Java線程池并發(fā)執(zhí)行多個任務(wù)方式

    Java線程池并發(fā)執(zhí)行多個任務(wù)方式

    這篇文章主要介紹了Java線程池并發(fā)執(zhí)行多個任務(wù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 深入解析Java的設(shè)計模式編程中的模板方法模式

    深入解析Java的設(shè)計模式編程中的模板方法模式

    這篇文章主要介紹了深入解析Java的設(shè)計模式編程中的模板方法模式, 模版方法模式由一個抽象類和一個(或一組)實現(xiàn)類通過繼承結(jié)構(gòu)組成,需要的朋友可以參考下
    2016-02-02
  • Java客戶端通過HTTPS連接到Easysearch實現(xiàn)過程

    Java客戶端通過HTTPS連接到Easysearch實現(xiàn)過程

    這篇文章主要為大家介紹了Java客戶端通過HTTPS連接到Easysearch實現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • Spring項目讀取配置文件中文亂碼的解決

    Spring項目讀取配置文件中文亂碼的解決

    這篇文章主要介紹了Spring項目讀取配置文件中文亂碼的解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • 詳解Java ArrayList類

    詳解Java ArrayList類

    這篇文章主要介紹了Java ArrayList類的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07

最新評論