問題精選-多線程

一、現(xiàn)在有 T1低缩、T2、T3 三個線程曹货,你怎樣保證 T2 在 T1 執(zhí)行完后執(zhí)行咆繁,T3 在 T2 執(zhí)行完后執(zhí)行?

這個線程問題通常會在第一輪或電話面試階段被問到顶籽,目的是檢測你對”join”方法是否熟悉玩般。這個多線程問題比較簡單,可以用 join 方法實現(xiàn)礼饱。

線程按順序執(zhí)行-方式一(這種方式更好坏为,不會阻塞主線程)

/**
 * @author Alan Chen
 * @description 線程按順序執(zhí)行-方式一
 * @date 2021/2/10
 */
public class ThreadOrder {
    public static void main(String[] args) {
        System.out.println("子線程調(diào)用前代碼邏輯......");

        Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(40);
                System.out.println("t1 run:");
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(40);
                t1.join();//表明當(dāng)前線程需要在t1線程上等待
                System.out.println("t2 run:");

            }
        });

        Thread t3 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(40);
                t2.join();//表明當(dāng)前線程需要在t2線程上等待
                System.out.println("t3 run:");

            }
        });

        t1.start();
        t2.start();
        t3.start();

        System.out.println("子線程調(diào)用后代碼邏輯......");
    }
}

執(zhí)行結(jié)果

子線程調(diào)用前代碼邏輯......
子線程調(diào)用后代碼邏輯......
t1 run:
t2 run:
t3 run:

線程按順序執(zhí)行-方式二

/**
 * @author Alan Chen
 * @description 線程按順序執(zhí)行-方式一
 * @date 2020-01-13DateTool
 */
public class TestClient {

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

        System.out.println("子線程調(diào)用前代碼邏輯......");

        Thread t1 = new Thread(new ThreadTest("t1"));
        Thread t2 = new Thread(new ThreadTest("t2"));
        Thread t3 = new Thread(new ThreadTest("t3"));

        t1.start();
        t1.join();

        t2.start();
        t2.join();

        t3.start();

        System.out.println("子線程調(diào)用后代碼邏輯......");
    }
}

class ThreadTest extends Thread {
    private String threadName;

    public ThreadTest(String name) {
        threadName = name;
    }

    @SneakyThrows
    @Override
    public void run() {
        Thread.sleep(40);
        System.out.println(threadName+" run:");
    }
}

執(zhí)行結(jié)果

子線程調(diào)用前代碼邏輯......
t1 run:
t2 run:
子線程調(diào)用后代碼邏輯......
t3 run:

join方法是synchronized,所以需要獲取Thread的對象鎖才能進(jìn)入镊绪,只有獲得了鎖才能調(diào)用wait放棄對鎖的獨占并等待再次獲取鎖匀伏。

join方法用線程對象調(diào)用,如果在一個線程A中調(diào)用另一個線程B的join方法蝴韭,線程A將會等待線程B執(zhí)行完畢后再執(zhí)行够颠。

join 方法是一個阻塞方法,用來進(jìn)行線程之間的交流榄鉴。線程 A 調(diào)用 線程 B 的 join 方法摧找,則線程 A 將阻塞核行,線程 B 執(zhí)行結(jié)束后 線程 A 開始執(zhí)行。

問:join方法的作用蹬耘?
答:Thread類中的join方法的主要作用就是同步芝雪,它可以使得線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行。當(dāng)我們調(diào)用某個線程的這個方法時综苔,這個方法會掛起調(diào)用線程惩系,直到被調(diào)用線程結(jié)束執(zhí)行,調(diào)用線程才會繼續(xù)執(zhí)行如筛。

問:join方法傳參和不傳參的區(qū)別堡牡?
答:join方法中如果傳入?yún)?shù),則表示這樣的意思:如果A線程中調(diào)用B線程的join(10)杨刨,則表示A線程會等待B線程執(zhí)行10毫秒晤柄,10毫秒過后,A妖胀、B線程并行執(zhí)行芥颈。需要注意的是,jdk規(guī)定赚抡,join(0)的意思不是A線程等待B線程0秒爬坑,而是A線程等待B線程無限時間,直到B線程執(zhí)行完畢涂臣,即join(0)等價于join()盾计。

問:join與start調(diào)用順序問題
答:join方法必須在線程start方法調(diào)用之后調(diào)用才有意義。這個也很容易理解:如果一個線程都沒有start赁遗,那它也就無法同步了署辉。因為執(zhí)行完start方法才會創(chuàng)建線程。

問:join方法實現(xiàn)原理
答:join方法是通過調(diào)用線程的wait方法來達(dá)到同步的目的的岩四。例如A線程中調(diào)用了B線程的join方法哭尝,則相當(dāng)于在A線程中調(diào)用了B線程的wait方法,當(dāng)B線程執(zhí)行完(或者到達(dá)等待時間)炫乓,B線程會自動調(diào)用自身的notifyAll方法喚醒A線程,從而達(dá)到同步的目的献丑。

二末捣、在 Java 中 Lock 接口比 synchronized 塊的優(yōu)勢是什么?你需要實現(xiàn)一個高效的緩存创橄,它允許多個用戶讀箩做,但只允許一個用戶寫,以此來保持它的完整性妥畏,你會怎樣去實現(xiàn)它邦邦?

lock 接口在多線程和并發(fā)編程中最大的優(yōu)勢是它們?yōu)樽x和寫分別提供了鎖安吁,它能滿足你寫像ConcurrentHashMap 這樣的高性能數(shù)據(jù)結(jié)構(gòu)和有條件的阻塞。Java 線程面試的問題越來越會根據(jù)面試者的回答來提問燃辖。我強(qiáng)烈建議在你去參加多線程的面試之前認(rèn)真讀一下 Locks鬼店,因為當(dāng)前其大量用于構(gòu)建電子交易終統(tǒng)的客戶端緩存和交易連接空間。

三黔龟、在 Java 中 wait 和 sleep 方法的不同妇智?

通常會在電話面試中經(jīng)常被問到的 Java 線程面試問題。最大的不同是在等待時 wait 會釋放鎖氏身,而 sleep 一直持有鎖巍棱。Wait 通常被用于線程間交互,sleep 通常被用于暫停執(zhí)行蛋欣。

四航徙、用 Java 實現(xiàn)阻塞隊列。

這是一個相對艱難的多線程面試問題陷虎,它能達(dá)到很多的目的到踏。第一,它可以檢測侯選者是否能實際的用 Java 線程寫程序泻红;第二夭禽,可以檢測侯選者對并發(fā)場景的理解,并且你可以根據(jù)這個問很多問題谊路。如果他用wait()和 notify()方法來實現(xiàn)阻塞隊列讹躯,你可以要求他用最新的Java5 中的并發(fā)類來再寫一次。

五缠劝、用 Java 寫代碼來解決生產(chǎn)者——消費者問題

與上面的問題很類似潮梯,但這個問題更經(jīng)典,有些時候面試都會問下面的問題惨恭。在 Java 中怎么解決生產(chǎn)者——消費者問題秉馏,當(dāng)然有很多解決方法,我已經(jīng)分享了一種用阻塞隊列實現(xiàn)的方法脱羡。有些時候他們甚至?xí)栐趺磳崿F(xiàn)哲學(xué)家進(jìn)餐問題萝究。

六、什么是原子操作锉罐,Java 中的原子操作是什么帆竹?

6.1 原子操作

原子操作是無法被別的線程打斷的操作。要么不執(zhí)行脓规,要么就執(zhí)行成功栽连。

例如:x=3是原子操作。過程就是先把工作內(nèi)存的x賦成3,再把主存的x賦成3秒紧。y=x不是原子操作绢陌,它涉及在工作內(nèi)存先把x值讀出來,再把這個值賦給y熔恢。x++或x=x+1也不是原子操作脐湾,它涉及取值,自加和賦值绩聘。

6.2 Java 中的原子操作

在Java中沥割,我們可以通過同步鎖或者CAS操作來實現(xiàn)原子操作。

CAS是Compare and swap的簡稱凿菩,這個操作是硬件級別的操作机杜,在硬件層面保證了操作的原子性。CAS有3個操作數(shù)衅谷,內(nèi)存值V椒拗,舊的預(yù)期值A(chǔ),要修改的新值B获黔。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時蚀苛,將內(nèi)存值V修改為B,否則什么都不做玷氏。Java中的sun.misc.Unsafe類提供了compareAndSwapInt和compareAndSwapLong等幾個方法實現(xiàn)CAS堵未。

七、Java 中的 volatile 關(guān)鍵是什么作用盏触?怎樣使用它渗蟹?在 Java 中它跟 synchronized 方法有什么不同?

自從 Java 5 和 Java 內(nèi)存模型改變以后赞辩,基于 volatile 關(guān)鍵字的線程問題越來越流行雌芽。應(yīng)該準(zhǔn)備好回答關(guān)于 volatile 變量怎樣在并發(fā)環(huán)境中確保可見性辨嗽。

八世落、多線程有什么用?

8.1 發(fā)揮多核 CPU 的優(yōu)勢

隨著工業(yè)的進(jìn)步糟需,現(xiàn)在的筆記本屉佳、臺式機(jī)乃至商用的應(yīng)用服務(wù)器至少也都是雙核的,4 核洲押、8 核甚至 16 核的也都不少見武花,如果是單線程的程序,那么在雙核 CPU 上就浪費了 50%诅诱,在 4 核 CPU 上就浪費了 75%髓堪。單核 CPU 上所謂的"多線程"那是假的多線程送朱,同一時間處理器只會處理一段邏輯瞪慧,只不過線程之間切換得比較快臀晃,看著像多個線程"同時"運行罷了养渴。多核 CPU 上的多線程才是真正的多線程,它能讓你的多段邏輯同時工作争群,多線程,可以真正發(fā)揮出多核 CPU 的優(yōu)勢來大年,達(dá)到充分利用 CPU 的目的换薄。

8.2 防止阻塞

從程序運行效率的角度來看,單核 CPU 不但不會發(fā)揮出多線程的優(yōu)勢翔试,反而會因為在單核 CPU 上運行多線程導(dǎo)致線程上下文的切換轻要,而降低程序整體的效率。但是單核 CPU 我們還是要應(yīng)用多線程垦缅,就是為了防止阻塞冲泥。試想,如果單核 CPU 使用單線程壁涎,那么只要這個線程阻塞了凡恍,比方說遠(yuǎn)程讀取某個數(shù)據(jù)吧,對端遲遲未返回又沒有設(shè)置超時時間怔球,那么你的整個程序在數(shù)據(jù)返回回來之前就停止運行了嚼酝。多線程可以防止這個問題,多條線程同時運行竟坛,哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻塞闽巩,也不會影響其它任務(wù)的執(zhí)行。

8.3 便于建模

這是另外一個沒有這么明顯的優(yōu)點了流码。假設(shè)有一個大的任務(wù) A又官,單線程編程,那么就要考慮很多漫试,建立整個程序模型比較麻煩六敬。但是如果把這個大的任務(wù) A分解成幾個小任務(wù),任務(wù) B驾荣、任務(wù) C外构、任務(wù) D,分別建立程序模型播掷,并通過多線程分別運行這幾個任務(wù)审编,那就簡單很多了。

九歧匈、創(chuàng)建線程的方式

9.1 繼承Thread類

看jdk源碼可以發(fā)現(xiàn)垒酬,Thread類其實是實現(xiàn)了Runnable接口的一個實例,繼承Thread類后需要重寫run方法并通過start方法啟動線程。繼承Thread類耦合性太強(qiáng)了勘究,因為java只能單繼承矮湘,所以不利于擴(kuò)展。

9.2 實現(xiàn)Runnable接口

通過實現(xiàn)Runnable接口并重寫run方法口糕,并把Runnable實例傳給Thread對象缅阳,Thread的start方法調(diào)用run方法再通過調(diào)用Runnable實例的run方法啟動線程。所以如果一個類繼承了另外一個父類景描,此時要實現(xiàn)多線程就不能通過繼承Thread的類實現(xiàn)十办。

9.3 實現(xiàn)Callable接口

通過實現(xiàn)Callable接口并重寫call方法,并把Callable實例傳給FutureTask對象超棺,再把FutureTask對象傳給Thread對象向族。它與Thread、Runnable最大的不同是Callable能返回一個異步處理的結(jié)果Future對象并能拋出異常棠绘,而其他兩種不能炸枣。

十、start()方法和 run()方法的區(qū)別

只有調(diào)用了 start()方法弄唧,才會表現(xiàn)出多線程的特性适肠,不同線程的 run()方法里面的代碼交替執(zhí)行。如果只是調(diào)用 run()方法候引,那么代碼還是同步執(zhí)行的侯养,必須等待一個線程的 run()方法里面的代碼全部執(zhí)行完畢之后,另外一個線程才可以執(zhí)行其 run()方法里面的代碼

十一澄干、Runnable 接口和 Callable 接口的區(qū)別

有點深的問題了逛揩,也看出一個 Java 程序員學(xué)習(xí)知識的廣度。Runnable 接口中的 run()方法的返回值是 void麸俘,它做的事情只是純粹地去執(zhí)行 run()方法中的代碼而已辩稽;Callable 接口中的 call()方法是有返回值的,是一個泛型从媚,和 Future逞泄、FutureTask 配合可以用來獲取異步執(zhí)行的結(jié)果。

這其實是很有用的一個特性拜效,因為多線程相比單線程更難喷众、更復(fù)雜的一個重要原因就是因為多線程充滿著未知性,某條線程是否執(zhí)行了紧憾?某條線程執(zhí)行了多久到千?某條線程執(zhí)行的時候我們期望的數(shù)據(jù)是否已經(jīng)賦值完畢?無法得知赴穗,我們能 做 的 只 是 等 待 這 條 多 線 程 的 任 務(wù) 執(zhí) 行 完 畢 而 已 憔四。 而Callable+Future/FutureTask 卻可以獲取多線程運行的結(jié)果膀息,可以在等待時間太長沒獲取到需要的數(shù)據(jù)的情況下取消該線程的任務(wù),真的是非常有用了赵。

十二履婉、CyclicBarrier 和 CountDownLatch 的區(qū)別

兩個看上去有點像的類,都在 java.util.concurrent 下斟览,都可以用來表示代碼運行到某個點上,二者的區(qū)別在于:

  • CyclicBarrier 的某個線程運行到某個點上之后辑奈,該線程即停止運行苛茂,直到所有的線程都到達(dá)了這個點,所有線程才重新運行鸠窗;CountDownLatch 則不是妓羊,某線程運行到某個點上之后,只是給某個數(shù)值-1 而已稍计,該線程繼續(xù)運行躁绸。

  • CyclicBarrier 只能喚起一個任務(wù),CountDownLatch 可以喚起多個任務(wù)臣嚣。

  • CyclicBarrier 可重用净刮,CountDownLatch 不可重用,計數(shù)值為 0 該CountDownLatch 就不可再用了硅则。

十三淹父、volatile 關(guān)鍵字的作用

一個非常重要的問題,是每個學(xué)習(xí)怎虫、應(yīng)用多線程的 Java 程序員都必須掌握的暑认。volatile 關(guān)鍵字的作用主要有兩個:

1、多線程主要圍繞可見性和原子性兩個特性而展開大审,使用 volatile 關(guān)鍵字修飾的變量蘸际,保證了其在多線程之間的可見性,即每次讀取到 volatile 變量徒扶,一定是最新的數(shù)據(jù)粮彤。

2、代碼底層執(zhí)行不像我們看到的高級語言----Java 程序這么簡單姜骡,它的執(zhí)行是 Java 代 碼 --> 字節(jié)碼 --> 根據(jù)字節(jié)碼執(zhí)行對應(yīng)的 C/C++ 代 碼-->C/C++代碼被編譯成匯編語言-->和硬件電路交互驾诈,現(xiàn)實中,為了獲取更好的性能 JVM 可能會對指令進(jìn)行重排序溶浴,多線程下可能會出現(xiàn)一些意想不到的問題乍迄。使用 volatile 則會對禁止語義重排序,當(dāng)然這也一定程度上降低了代碼執(zhí)行效率士败。

從實踐角度而言闯两,volatile 的一個重要作用就是和 CAS 結(jié)合褥伴,保證了原子性。

十四漾狼、什么是線程安全

如果你的代碼在多線程下執(zhí)行和在單線程下執(zhí)行永遠(yuǎn)都能獲得一樣的結(jié)果重慢,那么你的代碼就是線程安全的。這個問題有值得一提的地方逊躁,就是線程安全也是有幾個級別的:

14.1 不可變

像 String似踱、Integer、Long 這些稽煤,都是 final 類型的類轧简,任何一個線程都改變不了它們的值察藐,要改變除非新創(chuàng)建一個,因此這些不可變對象不需要任何同步手段就可以直接在多線程環(huán)境下使用惨寿。

14.2 絕對線程安全

不管運行時環(huán)境如何,調(diào)用者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java 中標(biāo)注自己是線程安全的類,實際上絕大多數(shù)都 不 是 線 程 安 全 的 蒸其, 不 過 絕 對 線 程 安 全 的 類 义屏, Java 中 也 有 , 比 方 說CopyOnWriteArrayList财喳、CopyOnWriteArraySet

14.3 相對線程安全

相對線程安全也就是我們通常意義上所說的線程安全,像 Vector 這種,add、remove 方法都是原子操作愈捅,不會被打斷青团,但也僅限于此,如果有個線程在遍歷某個 Vector瘪松、有個線程同時在 add 這個 Vector墅诡,99%的情況下都會出現(xiàn)ConcurrentModificationException然磷,也就是 fail-fast 機(jī)制寡润。

14.4 線程非安全

這個就沒什么好說的了变抽,ArrayList、LinkedList、HashMap 等都是線程非安全的類

十五立肘、Java 中如何獲取到線程 dump 文件

死循環(huán)边坤、死鎖、阻塞谅年、頁面打開慢等問題茧痒,打線程 dump 是最好的解決問題的途徑。所謂線程 dump 也就是線程堆棧融蹂,獲取到線程堆棧有兩步:

1旺订、獲取到線程的 pid弄企,可以通過使用 jps 命令,在 Linux 環(huán)境下還可以使用ps -ef | grep java

2区拳、打印線程堆棧拘领,可以通過使用 jstack pid 命令,在 Linux 環(huán)境下還可以使用 kill -3 pid

另外提一點樱调,Thread 類提供了一個 getStackTrace()方法也可以用于獲取線程堆棧约素。這是一個實例方法,因此此方法是和具體線程實例綁定的笆凌,每次獲取獲取到的是具體某個線程當(dāng)前運行的堆棧圣猎。

十六、如何在兩個線程之間共享數(shù)據(jù)

通 過 在 線 程 之 間 共 享 對 象 就 可 以 了 乞而, 然 后 通 過 wait/notify/notifyAll 送悔、await/signal/signalAll 進(jìn)行喚起和等待,比方說阻塞隊列 BlockingQueue就是為線程之間共享數(shù)據(jù)而設(shè)計的爪模。

十七欠啤、sleep 方法和 wait 方法有什么區(qū)別

這個問題常問,sleep 方法和 wait 方法都可以用來放棄 CPU 一定的時間呻右,不同點在于如果線程持有某個對象的監(jiān)視器跪妥,sleep 方法不會放棄這個對象的監(jiān)視器鞋喇,wait 方法會放棄這個對象的監(jiān)視器声滥。

十八、生產(chǎn)者消費者模型的作用是什么

1)通過平衡生產(chǎn)者的生產(chǎn)能力和消費者的消費能力來提升整個系統(tǒng)的運行效率侦香,這是生產(chǎn)者消費者模型最重要的作用落塑。

2)解耦,這是生產(chǎn)者消費者模型附帶的作用罐韩,解耦意味著生產(chǎn)者和消費者之間的聯(lián)系少,聯(lián)系越少越可以獨自發(fā)展而不需要收到相互的制約。

十九晦款、ThreadLocal 有什么用

簡單說 ThreadLocal 就是一種以空間換時間的做法,在每個 Thread 里面維護(hù)了一個以開地址法實現(xiàn)的 ThreadLocal.ThreadLocalMap缓溅,把數(shù)據(jù)進(jìn)行隔離,數(shù)據(jù)不共享赁温,自然就沒有線程安全方面的問題了袜匿。

二十、為什么 wait()方法和 notify()/notifyAll()方法要在同步塊中被調(diào)用

這是 JDK 強(qiáng)制的穆壕,wait()方法和 notify()/notifyAll()方法在調(diào)用前都必須先獲得對象的鎖缨该。

二十一熄云、wait()方法和 notify()/notifyAll()方法在放棄對象監(jiān)視器時有什么區(qū)別

wait()方法立即釋放對象監(jiān)視器膨更,notify()/notifyAll()方法則會等待線程剩余代碼執(zhí)行完畢才會放棄對象監(jiān)視器。

二十二、為什么要使用線程池

避免頻繁地創(chuàng)建和銷毀線程誊役,達(dá)到線程對象的重用。另外谷市,使用線程池還可以根據(jù)項目靈活地控制并發(fā)的數(shù)目蛔垢。

二十三、怎么檢測一個線程是否持有對象監(jiān)視器

Thread 類提供了一個 holdsLock(Object obj)方法歌懒,當(dāng)且僅當(dāng)對象 obj 的監(jiān)視器被某條線程持有的時候才會返回 true啦桌,注意這是一個static 方法,這意味著"某條線程"指的是當(dāng)前線程。

二十四甫男、synchronized 和 ReentrantLock 的區(qū)別

synchronized 是和 if且改、else、for板驳、while 一樣的關(guān)鍵字又跛,ReentrantLock是類,這是二者的本質(zhì)區(qū)別若治。既然 ReentrantLock 是類慨蓝,那么它就提供了比synchronized 更多更靈活的特性,可以被繼承端幼、可以有方法礼烈、可以有各種各樣的類變量,ReentrantLock 比 synchronized 的擴(kuò)展性體現(xiàn)在幾點上:

1婆跑、ReentrantLock 可以對獲取鎖的等待時間進(jìn)行設(shè)置此熬,這樣就避免了死鎖
2、ReentrantLock 可以獲取各種鎖的信息
3滑进、ReentrantLock 可以靈活地實現(xiàn)多路通知

另外犀忱,二者的鎖機(jī)制其實也是不一樣的。 ReentrantLock 底層調(diào)用的是Unsafe 的 park 方法加鎖扶关,synchronized 操作的應(yīng)該是對象頭中 mark word阴汇。

二十五、ConcurrentHashMap 的并發(fā)度是什么

ConcurrentHashMap 的并發(fā)度就是 segment 的大小节槐,默認(rèn)為 16搀庶,這意味著 最 多 同 時 可 以 有 16 條線程 操 作 ConcurrentHashMap ,這也是ConcurrentHashMap 對 Hashtable 的最大優(yōu)勢疯淫。

二十六地来、ReadWriteLock 是什么

如果使用 ReentrantLock戳玫,可能本身是為了防止線程 A 在寫數(shù)據(jù)熙掺、線程 B 在讀數(shù)據(jù)造成的數(shù)據(jù)不一致,但這樣咕宿,如果線程 C 在讀數(shù)據(jù)币绩、線程 D也在讀數(shù)據(jù),讀數(shù)據(jù)是不會改變數(shù)據(jù)的府阀,沒有必要加鎖缆镣,但是還是加鎖了,降低了程序的性能试浙。因為這個董瞻,才誕生了讀寫鎖 ReadWriteLock。ReadWriteLock 是一個讀寫鎖接口,ReentrantReadWriteLock 是 ReadWriteLock 接口的一個具體實現(xiàn)钠糊,實現(xiàn)了讀寫的分離挟秤,讀鎖是共享的,寫鎖是獨占的抄伍,讀和讀之間不會互斥艘刚,讀和寫、寫和讀截珍、寫和寫之間才會互斥攀甚,提升了讀寫的性能。

二十七岗喉、FutureTask 是什么

FutureTask 表示一個異步運算的任務(wù)秋度。FutureTask里面可以傳入一個 Callable 的具體實現(xiàn)類,可以對這個異步運算的任務(wù)的結(jié)果進(jìn)行等待獲取钱床、判斷是否已經(jīng)完成静陈、取消任務(wù)等操作。當(dāng)然诞丽,由于 FutureTask也是 Runnable 接口的實現(xiàn)類鲸拥,所以 FutureTask 也可以放入線程池中。

二十八僧免、Linux 環(huán)境下如何查找哪個線程使用 CPU 最長

1刑赶、獲取項目的 pid,jps 或者 ps -ef | grep java
2懂衩、top -H -p pid撞叨,順序不能改變這樣就可以打印出當(dāng)前的項目,每條線程占用 CPU 時間的百分比浊洞。注意這里
打出的是 LWP牵敷,也就是操作系統(tǒng)原生線程的線程號。

使用"top -H -p pid"+"jps pid"可以很容易地找到某條占用 CPU 高的線程的線程堆棧法希,從而定位占用 CPU 高的原因枷餐,一般是因為不當(dāng)?shù)拇a操作導(dǎo)致了死循環(huán)。最后提一點苫亦,"top -H -p pid"打出來的 LWP 是十進(jìn)制的毛肋,"jps pid"打出來的本地線程號是十六進(jìn)制的,轉(zhuǎn)換一下屋剑,就能定位到占用 CPU 高的線程的當(dāng)前線程堆棧了润匙。

二十九、什么是多線程的上下文切換

多線程的上下文切換是指 CPU 控制權(quán)由一個已經(jīng)正在運行的線程切換到另外一個就緒并等待獲取 CPU 執(zhí)行權(quán)的線程的過程唉匾。

三十孕讳、如果你提交任務(wù)時,線程池隊列已滿,這時會發(fā)生什么

1厂财、如果使用的是無界隊列 LinkedBlockingQueue油啤,也就是無界隊列的話,沒關(guān)系蟀苛,繼續(xù)添加任務(wù)到阻塞隊列中等待執(zhí)行益咬,因為 LinkedBlockingQueue 可以近乎認(rèn)為是一個無窮大的隊列,可以無限存放任務(wù)

2帜平、如果使用的是有界隊列比如 ArrayBlockingQueue幽告,任務(wù)首先會被添加到ArrayBlockingQueue 中 , ArrayBlockingQueue 滿 了 裆甩, 會 根 據(jù)maximumPoolSize 的值增加線程數(shù)量冗锁,如果增加了線程數(shù)量還是處理不過來,ArrayBlockingQueue 繼 續(xù) 滿嗤栓,那么則會使用拒絕策略RejectedExecutionHandler 處理滿了的任務(wù)冻河,默認(rèn)是 AbortPolicy

三十一、Java 中用到的線程調(diào)度算法是什么

搶占式茉帅。一個線程用完 CPU 之后叨叙,操作系統(tǒng)會根據(jù)線程優(yōu)先級、線程饑餓情況等數(shù)據(jù)算出一個總的優(yōu)先級并分配下一個時間片給某個線程執(zhí)行堪澎。

三十二擂错、Thread.sleep(0)的作用是什么

由于Java采用搶占式的線程調(diào)度算法,因此可能會出現(xiàn)某條線程常常獲取到CPU控制權(quán)的情況樱蛤,為了讓某些優(yōu)先級比較低的線程也能獲取到CPU控制權(quán)钮呀,可以使用Thread.sleep(0)手動觸發(fā)一次操作系統(tǒng)分配時間片的操作,這也是平衡CPU控制權(quán)的一種操作昨凡。

三十三爽醋、什么是自旋

很多 synchronized 里面的代碼只是一些很簡單的代碼,執(zhí)行時間非潮慵梗快蚂四,此時等待的線程都加鎖可能是一種不太值得的操作,因為線程阻塞涉及到用戶態(tài)和內(nèi)核態(tài)切換的問題就轧。既然 synchronized 里面的代碼執(zhí)行得非持ず迹快田度,不妨讓等待鎖的線程不要被阻塞妒御,而是在 synchronized 的邊界做忙循環(huán),這就是自旋镇饺。如果做了多次忙循環(huán)發(fā)現(xiàn)還沒有獲得鎖乎莉,再阻塞,這樣可能是一種更好的策略。

三十四惋啃、什么是 Java 內(nèi)存模型

Java 內(nèi)存模型定義了一種多線程訪問 Java 內(nèi)存的規(guī)范哼鬓。Java 內(nèi)存模型的幾部分內(nèi)容:

1、Java 內(nèi)存模型將內(nèi)存分為了主內(nèi)存和工作內(nèi)存边灭。類的狀態(tài)异希,也就是類之間共享的變量,是存儲在主內(nèi)存中的绒瘦,每次 Java 線程用到這些主內(nèi)存中的變量的時候称簿,會讀一次主內(nèi)存中的變量,并讓這些內(nèi)存在自己的工作內(nèi)存中有一份拷貝惰帽,運行自己線程代碼的時候憨降,用到這些變量,操作的都是自己工作內(nèi)存中的那一份该酗。在線程代碼執(zhí)行完畢之后授药,會將最新的值更新到主內(nèi)存中去。

2呜魄、定義了幾個原子操作悔叽,用于操作主內(nèi)存和工作內(nèi)存中的變量

3、定義了 volatile 變量的使用規(guī)則

4爵嗅、happens-before骄蝇,即先行發(fā)生原則,定義了操作 A 必然先行發(fā)生于操作B 的一些規(guī)則操骡,比如在同一個線程內(nèi)控制流前面的代碼一定先行發(fā)生于控制流后面的代碼九火、一個釋放鎖 unlock 的動作一定先行發(fā)生于后面對于同一個鎖進(jìn)行鎖定 lock 的動作等等,只要符合這些規(guī)則册招,則不需要額外做同步措施岔激,如果某段代碼不符合所有的 happens-before 規(guī)則,則這段代碼一定是線程非安全的是掰。

三十五虑鼎、什么是 CAS

CAS,全稱為 Compare and Swap键痛,即比較-替換炫彩。假設(shè)有三個操作數(shù):內(nèi)存值 V、舊的預(yù)期值 A絮短、要修改的值 B江兢,當(dāng)且僅當(dāng)預(yù)期值 A 和內(nèi)存值 V 相同時,才會將內(nèi)存值修改為 B 并返回 true丁频,否則什么都不做并返回 false杉允。當(dāng)然 CAS 一定要 volatile 變量配合邑贴,這樣才能保證每次拿到的變量是主內(nèi)存中最新的那個值,否則舊的預(yù)期值 A 對某條線程來說叔磷,永遠(yuǎn)是一個不會變的值 A拢驾,只要某次 CAS 操作失敗,永遠(yuǎn)都不可能成功改基。

三十六繁疤、什么是樂觀鎖和悲觀鎖

36.1 樂觀鎖

就像它的名字一樣,對于并發(fā)間操作產(chǎn)生的線程安全問題持樂觀狀態(tài)秕狰,樂觀鎖認(rèn)為競爭不總是會發(fā)生嵌洼,因此它不需要持有鎖,將比較-替換這兩個動作作為一個原子操作嘗試去修改內(nèi)存中的變量封恰,如果失敗則表示發(fā)生沖突麻养,那么就應(yīng)該有相應(yīng)的重試邏輯。

36.2 悲觀鎖

還是像它的名字一樣诺舔,對于并發(fā)間操作產(chǎn)生的線程安全問題持悲觀狀態(tài)鳖昌,悲觀鎖認(rèn)為競爭總是會發(fā)生,因此每次對某資源進(jìn)行操作時低飒,都會持有一個獨占的鎖许昨,就像 synchronized,不管三七二十一褥赊,直接上了鎖就操作資源了糕档。

三十七、什么是 AQS

簡單說一下 AQS拌喉,AQS 全稱為 AbstractQueuedSychronizer速那,翻譯過來應(yīng)該是抽象隊列同步器。如果說 java.util.concurrent 的基礎(chǔ)是 CAS 的話尿背,那么 AQS 就是整個 Java并發(fā)包的核心了端仰,ReentrantLock、CountDownLatch田藐、Semaphore 等等都用到了它荔烧。AQS 實際上以雙向隊列的形式連接所有的 Entry,比方說ReentrantLock汽久,所有等待的線程都被放在一個 Entry 中并連成雙向隊列鹤竭,前面一個線程使用 ReentrantLock 好了,則雙向隊列實際上的第一個 Entry開始運行景醇。AQS 定義了對雙向隊列所有的操作臀稚,而只開放了 tryLock 和 tryRelease 方法給開發(fā)者使用,開發(fā)者可以根據(jù)自己的實現(xiàn)重寫 tryLock 和 tryRelease 方法啡直,以實現(xiàn)自己的并發(fā)功能烁涌。

三十八苍碟、Hashtable 的 size()方法中明明只有一條語句"return count"酒觅,為什么還要做同步撮执?

1、同一時間只能有一條線程執(zhí)行固定類的同步方法舷丹,但是對于類的非同步方法抒钱,可以多條線程同時訪問。所以颜凯,這樣就有問題了谋币,可能線程 A 在執(zhí)行Hashtable 的 put 方法添加數(shù)據(jù),線程 B 則可以正常調(diào)用 size()方法讀取Hashtable 中當(dāng)前元素的個數(shù)症概,那讀取到的值可能不是最新的蕾额,可能線程 A添加了完了數(shù)據(jù),但是沒有對 size++彼城,線程 B 就已經(jīng)讀取 size 了诅蝶,那么對于線程 B 來說讀取到的 size 一定是不準(zhǔn)確的。而給 size()方法加了同步之后募壕,意味著線程 B 調(diào)用 size()方法只有在線程 A 調(diào)用 put 方法完畢之后才可以調(diào)用调炬,這樣就保證了線程安全性

2、CPU 執(zhí)行代碼舱馅,執(zhí)行的不是 Java 代碼缰泡,這點很關(guān)鍵。Java代碼最終是被翻譯成機(jī)器碼執(zhí)行的代嗤,機(jī)器碼才是真正可以和硬件電路交互的代碼棘钞。即使你看到 Java 代碼只有一行,甚至你看到 Java 代碼編譯之后生成的字節(jié)碼也只有一行干毅,也不意味著對于底層來說這句語句的操作只有一個武翎。一句"return count"假設(shè)被翻譯成了三句匯編語句執(zhí)行,一句匯編語句和其機(jī)器碼做對應(yīng)溶锭,完全可能執(zhí)行完第一句宝恶,線程就切換了。

三十九趴捅、同步方法和同步塊垫毙,哪個是更好的選擇

同步塊,這意味著同步塊之外的代碼是異步執(zhí)行的拱绑,這比同步整個方法更提升代碼的效率综芥。請知道一條原則:同步的范圍越小越好。

雖說同步的范圍越少越好猎拨,但是在 Java 虛擬機(jī)中還是存在著一種叫做鎖粗化的優(yōu)化方法膀藐,這種方法就是把同步范圍變大屠阻。這是有用的,比方說 StringBuffer额各,它是一個線程安全的類国觉,自然最常用的append()方法是一個同步方法,我們寫代碼的時候會反復(fù) append 字符串虾啦,這意味著要進(jìn)行反復(fù)的加鎖->解鎖麻诀,這對性能不利,因為這意味著 Java 虛擬機(jī)在這條線程上要反復(fù)地在內(nèi)核態(tài)和用戶態(tài)之間進(jìn)行切換傲醉,因此 Java 虛擬機(jī)會將多次 append 方法調(diào)用的代碼進(jìn)行一個鎖粗化的操作蝇闭,將多次的 append的操作擴(kuò)展到 append 方法的頭尾,變成一個大的同步塊硬毕,這樣就減少了加鎖-->解鎖的次數(shù)呻引,有效地提升了代碼執(zhí)行的效率。

四十吐咳、高并發(fā)逻悠、任務(wù)執(zhí)行時間短的業(yè)務(wù)怎樣使用線程池?并發(fā)不高挪丢、任務(wù)執(zhí)行時間長的業(yè)務(wù)怎樣使用線程池蹂风?并發(fā)高、業(yè)務(wù)執(zhí)行時間長的業(yè)務(wù)怎樣使用線程池乾蓬?

1惠啄、高并發(fā)、任務(wù)執(zhí)行時間短的業(yè)務(wù)任内,線程池線程數(shù)可以設(shè)置為 CPU 核數(shù)+1撵渡,減少線程上下文的切換

2、并發(fā)不高死嗦、任務(wù)執(zhí)行時間長的業(yè)務(wù)要區(qū)分開看:
a)假如是業(yè)務(wù)時間長集中在 IO 操作上趋距,也就是 IO 密集型的任務(wù),因為 IO操作并不占用 CPU越除,所以不要讓所有的 CPU 閑下來节腐,可以加大線程池中的線程數(shù)目,讓 CPU 處理更多的業(yè)務(wù)摘盆。

b)假如是業(yè)務(wù)時間長集中在計算操作上翼雀,也就是計算密集型任務(wù),這個就沒辦法了孩擂,和(1)一樣吧狼渊,線程池中的線程數(shù)設(shè)置得少一些,減少線程上下文的切換

3类垦、并發(fā)高狈邑、業(yè)務(wù)執(zhí)行時間長城须,解決這種類型任務(wù)的關(guān)鍵不在于線程池而在于整體架構(gòu)的設(shè)計,看看這些業(yè)務(wù)里面某些數(shù)據(jù)是否能做緩存是第一步米苹,增加服務(wù)器是第二步糕伐,至于線程池的設(shè)置,設(shè)置參考其他有關(guān)線程池的文章驱入。最后赤炒,業(yè)務(wù)執(zhí)行時間長的問題氯析,也可能需要分析一下亏较,看看能不能使用中間件對任務(wù)進(jìn)行拆分和解耦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掩缓,一起剝皮案震驚了整個濱河市雪情,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌你辣,老刑警劉巖巡通,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異舍哄,居然都是意外死亡宴凉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門表悬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弥锄,“玉大人,你說我怎么就攤上這事蟆沫∽严荆” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵饭庞,是天一觀的道長戒悠。 經(jīng)常有香客問我,道長舟山,這世上最難降的妖魔是什么绸狐? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮累盗,結(jié)果婚禮上寒矿,老公的妹妹穿的比我還像新娘。我一直安慰自己幅骄,他們只是感情好劫窒,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拆座,像睡著了一般主巍。 火紅的嫁衣襯著肌膚如雪冠息。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天孕索,我揣著相機(jī)與錄音逛艰,去河邊找鬼。 笑死搞旭,一個胖子當(dāng)著我的面吹牛散怖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肄渗,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼镇眷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤骇吭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后具伍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡圈驼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年人芽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩脆。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡萤厅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衙伶,到底是詐尸還是另有隱情祈坠,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布矢劲,位于F島的核電站赦拘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏芬沉。R本人自食惡果不足惜躺同,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丸逸。 院中可真熱鬧蹋艺,春花似錦、人聲如沸黄刚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涛救,卻和暖如春畏邢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背检吆。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工舒萎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹭沛。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓臂寝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親摊灭。 傳聞我的和親對象是個殘疾皇子咆贬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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