成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java 并發(fā)編程基礎(chǔ)小結(jié)

開發(fā)
不同的階段對于并發(fā)編程的禪修都有不一樣的理解,而本次的進(jìn)階將更多維度是去強(qiáng)調(diào)并發(fā)編程所需要關(guān)注的一些基礎(chǔ)問題和本質(zhì),希望對你有幫助。

一、并發(fā)編程中的一些核心思想

1. 為什么需要多線程

計(jì)算機(jī)發(fā)展初期都是以進(jìn)程為維度分配內(nèi)存、文件句柄以及安全證書等資源,同時多個進(jìn)程之間采用一些比較粗粒度的通信機(jī)制來交換數(shù)據(jù),包括:

  • 套接字
  • 信號處理器
  • 共享內(nèi)存

基于并發(fā)編程實(shí)戰(zhàn)的思想:

高效做事的人,總能在串行性和異步性之間找到一個合理的平衡點(diǎn),程序也是如此。

于是操作系統(tǒng)就引入多進(jìn)程運(yùn)行的調(diào)度機(jī)制,例如:在一個單核的計(jì)算機(jī)上進(jìn)程1得到CPU執(zhí)行權(quán),隨后進(jìn)入IO任務(wù)阻塞掛起,此時進(jìn)程2、進(jìn)程3先后在此阻塞期間獲得CPU執(zhí)行權(quán)執(zhí)行任務(wù):

基于上述基礎(chǔ)上,考慮到每一個進(jìn)程都獨(dú)有各自的內(nèi)存空間和文件句柄等資源,以如此龐大級別的單位處理一些單一的工作而在CPU之間進(jìn)行頻繁切換開銷是非常不客觀的,于是就有了輕量級調(diào)度單位——多線程。

以多線程調(diào)度為例,假設(shè)進(jìn)程1、進(jìn)程2分別對應(yīng)讀取定時讀取網(wǎng)絡(luò)數(shù)據(jù)、定時寫入數(shù)據(jù)到網(wǎng)絡(luò)系統(tǒng)日志,按照多線程維度將二者合并,最終的進(jìn)程交由CPU執(zhí)行,我們就可以得到這樣一個場景:

  • CPU執(zhí)行到線程1,讀取網(wǎng)絡(luò)數(shù)據(jù),IO阻塞,讓出CPU。
  • 線程2寫入之前的網(wǎng)絡(luò)系統(tǒng)日志到磁盤,進(jìn)行write調(diào)用時切換到內(nèi)核態(tài),讓出CPU。
  • 線程1完成數(shù)據(jù),進(jìn)程終端輸出結(jié)果,讓出CPU。
  • 線程2write調(diào)用返回,繼續(xù)進(jìn)行下一次的寫入......

2. 多線程有哪些優(yōu)勢

如上面所說,多線程存在如下優(yōu)勢:

  • 輕量:以線程為單位構(gòu)成進(jìn)程,共享進(jìn)程范圍內(nèi)的資源,例如內(nèi)存、文件句柄等。
  • 返回多核處理器的強(qiáng)大能力:操作系統(tǒng)以更輕量級的線程為單位進(jìn)行高效的調(diào)度和切換,在設(shè)計(jì)合理的情況下,可以大大提升CPU的利用率。
  • 建模簡單性:利用多線程技術(shù),可以將復(fù)雜的異步任務(wù)組合的同步工作流(例如JDK8中的CompleteFuture工具類),并利用多線程分別執(zhí)行這些任務(wù),在指定時機(jī)進(jìn)行同步交互。
  • 異步事件簡化處理:有了多線程的概念之后,早期嘗試過用BIO技術(shù)即一個線程分配一個客戶端socket,好在現(xiàn)代Unix系統(tǒng)提出epoll、io_uring的良好設(shè)計(jì),使得多線程技術(shù)有了更好的發(fā)揮。

3. 并發(fā)編程需要關(guān)注的問題

(1) 安全性問題

首先是線程安全性問題,因?yàn)槎嗑€程共享了一塊進(jìn)程的數(shù)據(jù),如果沒有充分的做好線程間的同步,就會出現(xiàn)一些意外的情況,就例如下面這段代碼,多線程操作一個num,因?yàn)樽栽霾僮鞣菑?fù)合操作且多線程操作彼此不可見,出現(xiàn)意外結(jié)果:

private staticint num = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            for (int i = 0; i < 100_0000; i++) {
                num++;
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 100_0000; i++) {
                num++;
            }
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();
        System.out.println(num);//輸出1499633

    }

同樣的,如果沒有良好的同步機(jī)制,編譯器、處理器都可以針對指令進(jìn)行任意順序和時間執(zhí)行,同時在處理器或者寄存器緩存線程變量的情況下的修改操作,其他處理器的線程是無法看到其修改操作,也會導(dǎo)致邏輯運(yùn)算上的錯亂:

(2) 活躍性問題

線程活躍性問題即線程未能按照預(yù)期的時許執(zhí)行,導(dǎo)致線程持續(xù)的活躍最典型的表現(xiàn)就是無限循環(huán),打滿CPU。例如并發(fā)環(huán)境下兩個CPU分別執(zhí)行線程0和線程1的邏輯,即:

  • 線程0執(zhí)行無限循環(huán),只要val變?yōu)閠rue則終止無限循環(huán),
  • 線程1休眠一段時間后將val修改為true。

對于java并發(fā)編程而言,如果沒有添加保證可見性的關(guān)鍵字進(jìn)行修飾,線程1的修改操作對于線程0來說是不可見的,此時就會出現(xiàn)下圖所示的線程1修改僅對自己可見,并不會即使刷新到CPU多核共享內(nèi)存L3 Cache,進(jìn)而導(dǎo)致線程0無限循環(huán),也就是我們所說的活躍性問題:

對應(yīng)我們也可以出示例代碼,同時筆者也會在后續(xù)的文章中來補(bǔ)充說明這一點(diǎn)的解決方案:

private staticboolean val = false;

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            while (!val) {//下方線程操作對于線程1不可見,進(jìn)行無限循環(huán)

            }
            System.out.println("thread-1 executed finished");
            countDownLatch.countDown();
        }).start();


        new Thread(() -> {
            ThreadUtil.sleep(5, TimeUnit.SECONDS);
            val = true;
            System.out.println("設(shè)置val為true");
            countDownLatch.countDown();

        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

通常來說活躍性問題都是由以下幾種錯誤導(dǎo)致:

  • 死鎖:即兩個線程互相等待對象持有的資源進(jìn)入阻塞
  • 活鎖:上述的活躍性問題就是最經(jīng)典的活鎖
  • 線程饑餓:因?yàn)榫€程過多或者某些原因?qū)е履硞€線程長時間未能分配到CPU時間片,導(dǎo)致任務(wù)遲遲無法結(jié)束,這就是典型的線程饑餓問題

(3) 性能問題

這一點(diǎn)是老生常態(tài)了,應(yīng)對并發(fā)安全的手段就是保證可見性和互斥,這涉及CPU緩存更新和臨界資源維度的把控和并發(fā)運(yùn)算技巧,一般來說導(dǎo)致多線程性能瓶頸的幾種原因可分為:

同步機(jī)制抑制了某些編譯器的優(yōu)化,例如synchronized關(guān)鍵字。

共享變量在多處理器之間不同線程執(zhí)行,線程切換時處理器的緩存數(shù)據(jù)局部性失效,使得開銷大部分時間都在處理線程調(diào)度而非運(yùn)算,這也會導(dǎo)致程序的執(zhí)行性能下降。

多線程并發(fā)處理時切換線程時產(chǎn)生保存和恢復(fù)上下文的開銷。

二、JVM視角下的進(jìn)程和線程

如下圖所示,可以看出線程是比進(jìn)程更小的單位,進(jìn)程是獨(dú)立的,彼此之間不會干擾,但是線程在同一個進(jìn)程中共享堆區(qū)和方法區(qū),雖然開銷較小,但是資源之間管理和分配處理相對于進(jìn)程之間要更加小心。

三、多線程常見問題

1. 程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧為什么線程中是各自獨(dú)立的

  • 程序計(jì)數(shù)器私有的原因:學(xué)過計(jì)算機(jī)組成原理的小伙伴應(yīng)該都知曉,程序計(jì)數(shù)器用于記錄當(dāng)前下一條要執(zhí)行的指令的單元地址,JVM也一樣,有了程序計(jì)數(shù)器才能保證在多線程的情況下,這個線程被掛起再被恢復(fù)時,我們可以根據(jù)程序計(jì)數(shù)器找到下一次要執(zhí)行的指令的位置。
  • 虛擬機(jī)棧私有的原因:每一個Java線程在執(zhí)行方法時,都會創(chuàng)建一個棧幀用于保存局部變量、常量池引用、操作數(shù)棧等信息,在這個方法調(diào)用到完成前,它對應(yīng)的信息都會基于棧幀保存在虛擬機(jī)棧上。
  • 本地方法棧私有的原因:和虛擬機(jī)棧類似,只不過本地方法棧保存的native方法的信息。

所以為了保證局部變量不被別的線程訪問到,虛擬機(jī)棧和本地方法棧都是私有的,這就是我們解決某些線程安全問題時,常會用到一個叫棧封閉技術(shù)。

關(guān)于棧封閉技術(shù)如下所示,將變量放在局部,每個線程都有自己的虛擬機(jī)棧,線程安全:

public class StackConfinement implements Runnable {

    //全部變量 多線操作會有現(xiàn)場問題
    int globalVariable = 0;

    public void inThread() {
        //棧封閉技術(shù),將變量放在局部,每個線程都有自己的虛擬機(jī)棧 線程安全
        int neverGoOut = 0;
        synchronized (this) {
            for (int i = 0; i < 10000; i++) {
                neverGoOut++;
            }
        }

        System.out.println("棧內(nèi)保護(hù)的數(shù)字是線程安全的:" + neverGoOut);//棧內(nèi)保護(hù)的數(shù)字是線程安全的:10000

    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            globalVariable++;
        }
        inThread();
    }

    public static void main(String[] args) throws InterruptedException {
        StackConfinement r1 = new StackConfinement();
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r1);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        
        System.out.println(r1.globalVariable); //13257
    }
}

2. 并發(fā)和并行的區(qū)別是什么?

  • 并發(fā):并發(fā)我們可以理解為,兩個線程先后執(zhí)行,但是從宏觀角度來看,他們幾乎是并行的。
  • 并行:并行我們可以理解為兩個線程同一時間都在運(yùn)行。

3. 同步和異步是什么意思?

  • 同步:同步就是一個調(diào)用沒有結(jié)果前,不會返回,直到有結(jié)果的才返回。
  • 異步:異步即發(fā)起一個調(diào)用后,不等結(jié)果如何直接返回。

4. 為什么需要多線程,多線程解決了什么問題

從宏觀角度來看:線程可以理解為輕量級進(jìn)程,切換開銷遠(yuǎn)遠(yuǎn)小于進(jìn)程,所以在多核CPU的計(jì)算機(jī)下,使用多線程可以更好的利用計(jì)算機(jī)資源從而提高計(jì)算機(jī)利用率和效率來應(yīng)對現(xiàn)如今的高并發(fā)網(wǎng)絡(luò)環(huán)境。

從微觀場景下來說: 單核場景,在單核CPU情況下,假如一個線程需要進(jìn)行IO才能執(zhí)行業(yè)務(wù)邏輯,若只有單線程,這就意味著IO期間發(fā)生阻塞線程卻只能干等。假如我們使用多線程的話,在當(dāng)前線程IO期間,我們可以將其掛起,讓出CPU時間片讓其他線程工作。

多核場景下,假如我們有一個很復(fù)雜的任務(wù)需要進(jìn)程各種IO和業(yè)務(wù)計(jì)算,假如只有一個線程的話,無論我們有多少個CPU核心,因?yàn)閱尉€程的緣故他永遠(yuǎn)只能利用一個CPU核心,假如我們使用多線程,那么這些線程就會映射到不同的CPU核心上,做到最好的利用計(jì)算機(jī)資源,提高執(zhí)行效率,執(zhí)行事件約為單線程執(zhí)行事件/CPU核心數(shù)。

5. 創(chuàng)建線程方式有哪些

直接繼承Thread啟動運(yùn)行:

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

    /**
     * 繼承thread重寫run方法
     */
    private static class Task extends Thread {
        @Override
        public void run() {
            Console.log("{} is running", Thread.currentThread().getName());
        }
    }

通過繼承Runable實(shí)現(xiàn)run方法并提交給thread運(yùn)行:

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

    /**
     * 繼承Runnable重寫run方法
     */
    private static class Task implements Runnable {
        @Override
        public void run() {
            Console.log("{} is running", Thread.currentThread().getName());
        }
    }

6. 為什么需要Runnable接口實(shí)現(xiàn)多線程

由于Java為避免棱形問題所以只支持單繼承,當(dāng)一個類已有繼承類時,某個函數(shù)需要實(shí)現(xiàn)異步功能的時候只能通過接口進(jìn)行拓展,所以才有了Runnable接口。

7. Thread和Runnable使用的區(qū)別

  • 繼承Thread:線程代碼存放在Thread子類的run方法中,調(diào)用start()即可實(shí)現(xiàn)調(diào)用。
  • Runnable:線程代碼存在接口子類的run方法中,需要實(shí)例化一個線程對象Thread并將其作為參數(shù)傳入,才能調(diào)用到run方法。

8. Thread類中run()和start()的區(qū)別

  • run:僅僅是方法,在線程實(shí)例化之后使用run等于一個普通對象的直接調(diào)用。
  • start:開啟了線程并執(zhí)行線程中的run方法,這期間程序才真正執(zhí)行從用戶態(tài)到內(nèi)核態(tài),創(chuàng)建線程的動作。

9. Java線程有哪幾種狀態(tài)

  • 新建(NEW):新創(chuàng)建的了一個線程對象,該對象并沒有調(diào)用start()。
  • 可運(yùn)行(RUNNABLE):線程對象創(chuàng)建后,并調(diào)用了start方法,等待分配CPU時間執(zhí)行代碼邏輯。
  • 阻塞(BLOCKED):阻塞狀態(tài),等待鎖的釋放。當(dāng)線程在synchronized 中被wait,然后再被喚醒時,若synchronized 有其他線程在執(zhí)行,那么它就會進(jìn)入BLOCKED狀態(tài)。
  • 等待(WAITING):因?yàn)槟承┰虮粧炱穑却渌€程通知或者喚醒。
  • 超時等待(TIME_WAITING):等待時間后自行返回,而不像WAITING那樣沒有通知就一直等待。
  • 終止(TERMINATED):該線程執(zhí)行完畢,終止?fàn)顟B(tài)了。
public enum State {
       //線程尚未啟動
        NEW,

        //可運(yùn)行的線程狀態(tài),該狀態(tài)代表的是操作系統(tǒng)中線程狀態(tài)的ready或者running狀態(tài)
        RUNNABLE,

        //阻塞等待監(jiān)視器(synchronized底層的monitor lock實(shí)現(xiàn))或者主動調(diào)用wait后被喚醒等待獲取監(jiān)視鎖也會處于該狀態(tài)
        BLOCKED,

       //調(diào)用wait掛起等待notify或者notifyAll
        WAITING,

       //設(shè)置時限的wait調(diào)用掛起,可能是調(diào)用下面某個方法
       //Thread.sleep
    //Object.wait with timeout
    //Thread.join with timeout
    //LockSupport.parkNanos
    //LockSupport.parkUntil
        TIMED_WAITING,

        //線程已完成執(zhí)行并終止
        TERMINATED;
    }

10. 和操作系統(tǒng)的線程狀態(tài)的區(qū)別

如下圖所示,實(shí)際上操作系統(tǒng)層面可將RUNNABLE分為Running以及Ready,Java設(shè)計(jì)者之所以沒有區(qū)分那么細(xì)是因?yàn)楝F(xiàn)代計(jì)算機(jī)執(zhí)行效率非常高,這兩個狀態(tài)在宏觀角度幾乎無法感知。現(xiàn)代操作系統(tǒng)對多線程采用時間分片的搶占式調(diào)度算法,使得每個線程得到CPU在10-20ms 處于運(yùn)行狀態(tài),然后在讓出CPU時間片,在不久后又會被調(diào)度執(zhí)行,所以對于這種微觀狀態(tài)區(qū)別,Java設(shè)計(jì)者認(rèn)為沒有必要為了這么一瞬間進(jìn)行這么多的狀態(tài)劃分。

11. 什么是上下文切換

線程在執(zhí)行過程中都會有自己的運(yùn)行條件和狀態(tài),這些運(yùn)行條件和狀態(tài)我們就稱之為線程上下文,這些信息例如程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧等信息。當(dāng)出現(xiàn)以下幾種情況的時候就會從占用CPU狀態(tài)中退出:

  • 線程主動讓出CPU,例如調(diào)用wait或者sleep等方法。
  • 線程的CPU 時間片用完 而退出CPU占用狀態(tài) (因?yàn)椴僮飨到y(tǒng)為了避免某些線程獨(dú)占CPU導(dǎo)致其他線程饑餓的情況就設(shè)定的例如時間分片算法)。
  • 線程調(diào)用了阻塞類型的系統(tǒng)中斷,例如IO請求等。
  • 線程被終止或者結(jié)束運(yùn)行。

上述的前三種情況都會發(fā)生上下文切換。為了保證線程被切換在恢復(fù)時能夠繼續(xù)執(zhí)行,所以上下文切換都需要保存線程當(dāng)前執(zhí)行的信息,并恢復(fù)下一個要執(zhí)行線程的現(xiàn)場。這種操作就會占用CPU和內(nèi)存資源,頻繁的進(jìn)行上下文切換就會導(dǎo)致整體效率低下。

12. 線程死鎖問題

如下圖所示,兩個線程各自持有一把鎖,必須拿到對方手中那把鎖才能釋放自己的鎖,正是這樣一種雙方僵持的狀態(tài)就會導(dǎo)致線程死鎖問題。

翻譯稱代碼就如下圖所示:

public class DeadLockDemo {
    publicstaticfinal Object lock1 = new Object();

    publicstaticfinal Object lock2 = new Object();


    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1){
                System.out.println("線程1獲得鎖1,準(zhǔn)備獲取鎖2");


                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("線程1獲得鎖2");
                }
            }
        }).start();


        new Thread(() -> {
            synchronized (lock2){
                System.out.println("線程2獲得鎖2,準(zhǔn)備獲取鎖1");

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                synchronized (lock1){
                    System.out.println("線程2獲得鎖1");
                }
            }
        }).start();
    }
}

輸出結(jié)果:

線程1獲得鎖1,準(zhǔn)備獲取鎖2
線程2獲得鎖2,準(zhǔn)備獲取鎖1

符合以下4個條件的場景就會發(fā)生死鎖問題:

  • 互斥:一個資源任意時間只能被一個線程獲取。
  • 請求與保持條件:一個線程拿到資源后,在獲取其他資源而進(jìn)入阻塞期間,不會釋放已有資源。
  • 不可剝奪條件:該資源被線程使用時,其他線程無法剝奪該線程使用權(quán),除非這個線程主動釋放。
  • 循環(huán)等待條件:若干線程獲取資源時,取鎖的流程構(gòu)成一個頭尾相接的環(huán),如上圖。

預(yù)防死鎖的幾種方式:

  • 破壞請求與保持條件:以上面代碼為例,我們要求所有線程必須一次性獲得兩個鎖才能進(jìn)行業(yè)務(wù)處理。即要求線程一次性獲得所有資源才能進(jìn)行邏輯處理。
  • 破壞不可剝奪:資源被其他線程獲取時,我們可以強(qiáng)行剝奪使用權(quán)。
  • 破壞循環(huán)等待:這個就比較巧妙了,例如我們上面lock1 id為1,lock2id為2,我們讓每個線程取鎖時都按照lock的id順序取鎖,這樣就避免構(gòu)成循環(huán)隊(duì)列。
  • 操作系統(tǒng)思想(銀行家算法):這個就涉及到操作系統(tǒng)知識了,大抵的意思是在取鎖之前對資源分配進(jìn)行評估,如果在給定資源情況下不能完成業(yè)務(wù)邏輯,那么就避免這個線程取鎖,感興趣的讀者可以

13. sleep和wait方法區(qū)別

  • sleep不會釋放鎖,只是單純休眠一會。而wait則會釋放鎖。
  • sleep單純讓線程休眠,在給定時間后就會蘇醒,而wait若沒有設(shè)定時間的話,只能通過notify或者notifyAll喚醒。
  • sleep是Thread 的方法,而wait是Object 的方法
  • wait常用于線程之間的通信或者交互,而sleep單純讓線程讓出執(zhí)行權(quán)。

14. 為什么sleep會定義在Thread

因?yàn)閟leep要做的僅僅是讓線程休眠,所以不涉及任何鎖釋放等邏輯,放在Thread上最合適。

15. 為什么wait會定義在Object 上

我們都知道使用wait時就會釋放鎖,并讓對象進(jìn)入WAITING 狀態(tài),會涉及到資源釋放等問題,所以我們需要將wait放在Object 類上。

16. 可以直接調(diào)用 Thread 類的 run 方法嗎?

若我們編寫run方法,然后調(diào)用Thread 的start方法,線程就會從用戶態(tài)轉(zhuǎn)內(nèi)核態(tài)創(chuàng)建線程,并在獲取CPU時間片的時候開始運(yùn)行,然后運(yùn)行run方法。 若直接調(diào)用run方法,那么該方法和普通方法沒有任何差別,它僅僅是一個名字為run的普通方法。

17. 假如在進(jìn)程中, 已經(jīng)開辟了多個線程,  其中一個線程怎么中斷其它線程?

找到線程對應(yīng)線程組并基于線程id即可定位到線程,然后調(diào)用interrupt將其打斷即可:

public static Thread getThreadById(long threadId) {
        //獲取線程對應(yīng)線程組
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        //比對id定位線程
        if (threadGroup != null) {
            Thread[] threads = new Thread[(int) (threadGroup.activeCount() * 1.2)];
            //獲取線程組中獲取的線程數(shù)
            int count = threadGroup.enumerate(threads, true);
            for (int i = 0; i < count; i++) {
                if (threads[i].getId() == threadId) {
                    return threads[i];
                }
            }
        }

        thrownew RuntimeException("未找到線程");
    }

對應(yīng)的我們也給出使用示例,感興趣的讀者可自行參閱注釋了解實(shí)現(xiàn)細(xì)節(jié):

//創(chuàng)建含有2個線程的線程池
    privatestaticfinal ExecutorService threadPool = Executors.newFixedThreadPool(2);
    //記錄用于打斷的線程id
    privatestatic Long threadId;


    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        //線程1無限休眠,直到被打斷
        threadPool.execute(() -> {
            Console.log("線程池線程啟動執(zhí)行,線程id:{}", Thread.currentThread().getId());
            threadId = Thread.currentThread().getId();
            try {
                TimeUnit.DAYS.sleep(1);
            } catch (InterruptedException e) {
                Console.error("當(dāng)前線程被打斷,線程id:{}", Thread.currentThread().getId(), e);
            } finally {
                countDownLatch.countDown();
            }
        });

        //線程2用于打斷線程1
        threadPool.execute(() -> {
            while (true) {
                if (threadId != null) {
                    Console.log("打斷線程,線程id:{}", threadId);
                    getThreadById(threadId).interrupt();
                    countDownLatch.countDown();
                    break;
                }
                ThreadUtil.sleep(5000);
            }
        });

        countDownLatch.await();
        threadPool.shutdownNow();
    }

對應(yīng)輸出結(jié)果如下,可以看到threadId 非空時,線程2就會將休眠的線程1打斷:

18. IO阻塞的線程會占用CPU資源嗎?如何避免線程霸占CPU?

由于該問題的篇幅比較大,筆者專門寫了一篇文章來討論這兩個問題,感興趣的朋友可以看看:《IO任務(wù)與CPU調(diào)度藝術(shù)

責(zé)任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關(guān)推薦

2021-02-26 13:08:27

Java高并發(fā)AQS

2019-11-07 09:20:29

Java線程操作系統(tǒng)

2021-03-11 00:05:55

Java高并發(fā)編程

2021-03-18 00:14:29

JavaCyclicBarri高并發(fā)

2021-03-04 07:24:24

JavaSemaphore高并發(fā)

2017-09-19 14:53:37

Java并發(fā)編程并發(fā)代碼設(shè)計(jì)

2025-02-17 00:00:25

Java并發(fā)編程

2025-02-19 00:05:18

Java并發(fā)編程

2011-12-29 13:31:15

Java

2014-05-20 16:27:35

JVMScala

2023-07-03 09:59:00

并發(fā)編程并發(fā)容器

2011-06-08 15:21:18

多維數(shù)組

2024-04-29 09:06:46

線程初始化源碼

2018-12-18 14:08:01

Java內(nèi)存volatile

2025-03-26 00:55:00

2011-07-21 10:17:53

java

2025-01-10 07:10:00

2025-02-06 03:14:38

2012-03-09 10:44:11

Java

2025-03-20 06:48:55

性能優(yōu)化JDK
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 日韩精品久久久久 | 国产免费一区二区 | 成人一区二区视频 | 国产精品视频一区二区三 | 中文字幕精品一区 | 成人一区二区三区 | 日韩精品一区二 | 中文字幕国产精品 | 在线成人av | 国产精品久久久久久福利一牛影视 | www.99热这里只有精品 | 国产精品久久久久久久久久三级 | 蜜桃视频在线观看免费视频网站www | 亚洲黄色一级毛片 | 青青久在线视频 | 国产99久久精品 | 亚洲三级av | 岛国视频 | 国产精品美女久久久久久久网站 | 亚洲色图插插插 | 男女羞羞免费网站 | av在线一区二区 | 国产自产21区 | 成人二区| 日韩欧美一区二区三区四区 | 亚州影院 | 免费久久视频 | 国产一区二区高清在线 | 欧美日韩精品一区二区三区蜜桃 | 免费黄色在线观看 | 成人夜晚看av | 在线91 | a国产视频| 国产999精品久久久 日本视频一区二区三区 | 四虎成人免费电影 | 国产精品久久av | 国产成人免费在线 | 一区二区三区成人 | 久久亚洲春色中文字幕久久久 | 亚洲 欧美 精品 | 久久狠狠 |