1. 線程和進(jìn)程的區(qū)別?
它們是不同的操作系統(tǒng)資源管理方式。進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程。
- 簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.
- 線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。
- 另外,進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。
- 線程在執(zhí)行過(guò)程中與進(jìn)程還是有區(qū)別的。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
- 從邏輯角度來(lái)看,多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線程的重要區(qū)別。
2. 實(shí)現(xiàn)線程有哪幾種方式?
(1)實(shí)現(xiàn)Runnable接口
(2)繼承Thread類
(3)通過(guò)Callable和Future創(chuàng)建線程
創(chuàng)建線程的三種方式的對(duì)比:
(1)采用實(shí)現(xiàn) Runnable、Callable 接口的方式創(chuàng)見(jiàn)多線程時(shí),線程類只是實(shí)現(xiàn)了 Runnable 接口或 Callable 接口,還可以繼承其他類。
(2)使用繼承 Thread 類的方式創(chuàng)建多線程時(shí),編寫(xiě)簡(jiǎn)單,如果需要訪問(wèn)當(dāng)前線程,則無(wú)需使用 Thread.currentThread() 方法,直接使用 this 即可獲得當(dāng)前線程。
3. 線程有哪幾種狀態(tài)?它們之間如何流轉(zhuǎn)的?
新建狀態(tài):
使用 new 關(guān)鍵字和 Thread 類或其子類建立一個(gè)線程對(duì)象后,該線程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程。
就緒狀態(tài):
當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。
運(yùn)行狀態(tài):
如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行 run(),此時(shí)線程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。
阻塞狀態(tài):
如果一個(gè)線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)。可以分為三種:
等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進(jìn)入到等待阻塞狀態(tài)。
同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)。
其他阻塞:通過(guò)調(diào)用線程的 sleep() 或 join() 發(fā)出了 I/O 請(qǐng)求時(shí),線程就會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep() 狀態(tài)超時(shí),join() 等待線程終止或超時(shí),或者 I/O 處理完畢,線程重新轉(zhuǎn)入就緒狀態(tài)。
死亡狀態(tài):
一個(gè)運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時(shí),該線程就切換到終止?fàn)顟B(tài)。
4. 線程中的start()和run()方法有什么區(qū)別?
- start() 可以啟動(dòng)一個(gè)新線程,run()不能
- start()不能被重復(fù)調(diào)用,run()可以
- start()中的run代碼可以不執(zhí)行完就繼續(xù)執(zhí)行下面的代碼,即進(jìn)行了線程切換。直接調(diào)用run方法必須等待其代碼全部執(zhí)行完才能繼續(xù)執(zhí)行下面的代碼。
- start() 實(shí)現(xiàn)了多線程,run()沒(méi)有實(shí)現(xiàn)多線程。
5. 怎么終止一個(gè)線程?如何優(yōu)雅地終止線程?
- 使用退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止;
- 使用stop方法強(qiáng)行終止線程;
- 使用interrupt方法中斷線程。
6. ThreadLocal在多線程中扮演什么角色?
為了解決多線程中相同變量的訪問(wèn)沖突問(wèn)題。
ThreadLocal為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本,從而隔離了多個(gè)線 程對(duì)訪問(wèn)數(shù)據(jù)的沖突。因?yàn)槊恳粋€(gè)線程都擁有自己的變量副本,從而也就沒(méi)有必要對(duì)該變量進(jìn)行同步了。ThreadLocal提供了線程安全的對(duì)象封裝,在編寫(xiě)多線程代碼時(shí),可以把不安全的變量封裝進(jìn)ThreadLocal。
線程并發(fā)時(shí),使用ThreadLocal在保證每個(gè)線程擁有自己的獨(dú)立對(duì)象,線程間互不影響。
“以空間換時(shí)間”的方式:訪問(wèn)并行化,對(duì)象獨(dú)享化。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn)。
7. 線程中的wait()和sleep()方法有什么區(qū)別?
sleep與wait都可以使線程等待,但sleep不會(huì)釋放資源而wait會(huì)釋放資源。
還有就是,wait方法只能在同步塊或者同步方法中執(zhí)行。
8. 多線程同步有哪幾種方法?
多線程并發(fā),當(dāng)多個(gè)線程同時(shí)操作一個(gè)可共享的資源變量時(shí)(如數(shù)據(jù)的增刪改查), 將會(huì)導(dǎo)致數(shù)據(jù)不準(zhǔn)確,相互之間產(chǎn)生沖突,因此加入同步鎖以避免在該線程沒(méi)有完成操作之前,被其他線程的調(diào)用, 從而保證了該變量的唯一性和準(zhǔn)確性。
8.1 同步方法
即有synchronized關(guān)鍵字修飾的方法。
由于java的每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖,當(dāng)用此關(guān)鍵字修飾方法時(shí),
內(nèi)置鎖會(huì)保護(hù)整個(gè)方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。
public synchronized void save()
{
...
}
注: synchronized關(guān)鍵字也可以修飾靜態(tài)方法,此時(shí)如果調(diào)用該靜態(tài)方法,將會(huì)鎖住整個(gè)類。
8.2 同步代碼塊
即有synchronized關(guān)鍵字修飾的語(yǔ)句塊。
被該關(guān)鍵字修飾的語(yǔ)句塊會(huì)自動(dòng)被加上內(nèi)置鎖,從而實(shí)現(xiàn)同步。
8.3 使用特殊域變量(volatile)
a.volatile關(guān)鍵字為域變量的訪問(wèn)提供了一種免鎖機(jī)制;
b.使用volatile修飾域相當(dāng)于告訴虛擬機(jī)該域可能會(huì)被其他線程更新;
c.因此每次使用該域就要重新計(jì)算,而不是使用寄存器中的值;
d.volatile不會(huì)提供任何原子操作,它也不能用來(lái)修飾final類型的變量。
8.4 使用重入鎖
8.5 使用局部變量(ThreadLocal)
9. 什么是死鎖?如何避免死鎖?
死鎖是指多個(gè)進(jìn)程循環(huán)等待它方占有的資源而無(wú)限期地僵持下去的局面。
使產(chǎn)生死鎖的四個(gè)必要條件不能同時(shí)具備;
- 安全序列
- 銀行家算法
10. 多線程之間如何進(jìn)行通信?
- 鎖機(jī)制:包括互斥鎖、條件變量、讀寫(xiě)鎖
互斥鎖提供了以排他方式防止數(shù)據(jù)結(jié)構(gòu)被并發(fā)修改的方法。
讀寫(xiě)鎖允許多個(gè)線程同時(shí)讀共享數(shù)據(jù),而對(duì)寫(xiě)操作是互斥的。
條件變量可以以原子的方式阻塞進(jìn)程,直到某個(gè)特定條件為真為止。對(duì)條件的測(cè)試是在互斥鎖的保護(hù)下進(jìn)行的。條件變量始終與互斥鎖一起使用。 - 信號(hào)量機(jī)制(Semaphore):包括無(wú)名線程信號(hào)量和命名線程信號(hào)量
- 信號(hào)機(jī)制(Signal):類似進(jìn)程間的信號(hào)處理
線程間的通信目的主要是用于線程同步,所以線程沒(méi)有像進(jìn)程通信中的用于數(shù)據(jù)交換的通信機(jī)制。
11. 線程怎樣返回結(jié)果?如何獲取?
- 實(shí)現(xiàn)Callable接口,獲取一個(gè)Future的對(duì)象,在該對(duì)象上調(diào)用get就可以獲取返回結(jié)果;
- 通過(guò)回調(diào)函數(shù)返回?cái)?shù)據(jù)。
12. violatile關(guān)鍵字有什么用,和synchronized有什么區(qū)別?
它所修飾的變量不保留拷貝,直接訪問(wèn)主內(nèi)存中的。
在Java內(nèi)存模型中,有main memory,每個(gè)線程也有自己的memory (例如寄存器)。為了性能,一個(gè)線程會(huì)在自己的memory中保持要訪問(wèn)的變量的副本。這樣就會(huì)出現(xiàn)同一個(gè)變 量在某個(gè)瞬間,在一個(gè)線程的memory中的值可能與另外一個(gè)線程memory中的值,或者main memory中的值不一致的情況。 一個(gè)變量聲明為volatile,就意味著這個(gè)變量是隨時(shí)會(huì)被其他線程修改的,因此不能將它c(diǎn)ache在線程memory中。
區(qū)別:
- volatile是變量修飾符,而synchronized則作用于一段代碼或方法。
- volatile只是在線程內(nèi)存和“主”內(nèi)存間同步某個(gè)變量的值;而synchronized通過(guò)鎖定和解鎖某個(gè)監(jiān)視器同步所有變量的值。顯然synchronized要比volatile消耗更多資源。
13. 假如新建T1、T2、T3三個(gè)線程,如何保證它們按順序執(zhí)行?
可以用線程類的join()方法在一個(gè)線程中啟動(dòng)另一個(gè)線程,另一個(gè)線程完成。
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
final Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t1.start();
t2.start();
t3.start();
14. 怎么控制同一時(shí)間只有3個(gè)線程運(yùn)行?
Semaphore 控制并發(fā)訪問(wèn)線程數(shù)。
15. 為什么要使用線程池?
在Java中,如果每當(dāng)一個(gè)請(qǐng)求到達(dá)就創(chuàng)建一個(gè)新線程,開(kāi)銷是相當(dāng)大的。在實(shí)際使用中,每個(gè)請(qǐng)求創(chuàng)建新線程的服務(wù)器在創(chuàng)建和銷毀線程上花費(fèi)的時(shí)間和消耗的系統(tǒng)資源,甚至可能要比花在處理實(shí)際的用戶請(qǐng)求的時(shí)間和資源要多得多。除了創(chuàng)建和銷毀線程的開(kāi)銷之外,活動(dòng)的線程也需要消耗系統(tǒng)資源。如果在一個(gè)JVM里創(chuàng)建太多的線程,可能會(huì)導(dǎo)致系統(tǒng)由于過(guò)度消耗內(nèi)存或“切換過(guò)度”而導(dǎo)致系統(tǒng)資源不足。為了防止資源不足,服務(wù)器應(yīng)用程序需要一些辦法來(lái)限制任何給定時(shí)刻處理的請(qǐng)求數(shù)目,盡可能減少創(chuàng)建和銷毀線程的次數(shù),特別是一些資源耗費(fèi)比較大的線程的創(chuàng)建和銷毀,盡量利用已有對(duì)象來(lái)進(jìn)行服務(wù),這就是“池化資源”技術(shù)產(chǎn)生的原因。
線程池主要用來(lái)解決線程生命周期開(kāi)銷問(wèn)題和資源不足問(wèn)題。通過(guò)對(duì)多個(gè)任務(wù)重用線程,線程創(chuàng)建的開(kāi)銷就被分?jǐn)偟搅硕鄠€(gè)任務(wù)上了,而且由于在請(qǐng)求到達(dá)時(shí)線程已經(jīng)存在,所以消除了線程創(chuàng)建所帶來(lái)的延遲。這樣,就可以立即為請(qǐng)求服務(wù),使應(yīng)用程序響應(yīng)更快。另外,通過(guò)適當(dāng)?shù)卣{(diào)整線程池中的線程數(shù)目可以防止出現(xiàn)資源不足的情況。
16. 說(shuō)一說(shuō)常用的幾種線程池并講講其中的工作原理。
- newFixedThreadPool
創(chuàng)建一個(gè)指定工作線程數(shù)量的線程池。每當(dāng)提交一個(gè)任務(wù)就創(chuàng)建一個(gè)工作線程,如果工作線程數(shù)量達(dá)到線程池初始的最大數(shù),則將提交的任務(wù)存入到池隊(duì)列中。
FixedThreadPool是一個(gè)典型且優(yōu)秀的線程池,它具有線程池提高程序效率和節(jié)省創(chuàng)建線程時(shí)所耗的開(kāi)銷的優(yōu)點(diǎn)。但是,在線程池空閑時(shí),即線程池中沒(méi)有可運(yùn)行任務(wù)時(shí),它不會(huì)釋放工作線程,還會(huì)占用一定的系統(tǒng)資源。 - newCachedThreadPool
創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
這種類型的線程池特點(diǎn)是:
工作線程的創(chuàng)建數(shù)量幾乎沒(méi)有限制(其實(shí)也有限制的,數(shù)目為Interger. MAX_VALUE), 這樣可靈活的往線程池中添加線程。
如果長(zhǎng)時(shí)間沒(méi)有往線程池中提交任務(wù),即如果工作線程空閑了指定的時(shí)間(默認(rèn)為1分鐘),則該工作線程將自動(dòng)終止。終止后,如果你又提交了新的任務(wù),則線程池重新創(chuàng)建一個(gè)工作線程。
在使用CachedThreadPool時(shí),一定要注意控制任務(wù)的數(shù)量,否則,由于大量線程同時(shí)運(yùn)行,很有會(huì)造成系統(tǒng)癱瘓。 - newSingleThreadExecutor
創(chuàng)建一個(gè)單線程化的Executor,即只創(chuàng)建唯一的工作者線程來(lái)執(zhí)行任務(wù),它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。如果這個(gè)線程異常結(jié)束,會(huì)有另一個(gè)取代它,保證順序執(zhí)行。單工作線程最大的特點(diǎn)是可保證順序地執(zhí)行各個(gè)任務(wù),并且在任意給定的時(shí)間不會(huì)有多個(gè)線程是活動(dòng)的。
4) newScheduleThreadPool
創(chuàng)建一個(gè)定長(zhǎng)的線程池,而且支持定時(shí)的以及周期性的任務(wù)執(zhí)行,支持定時(shí)及周期性任務(wù)執(zhí)行。
17. 線程池啟動(dòng)線程submit()和execute()有什么不同?
- execute提交的方式只能提交一個(gè)Runnable的對(duì)象,且該方法的返回值是void,也即是提交后如果線程運(yùn)行后,和主線程就脫離了關(guān)系了,當(dāng)然可以設(shè)置一些變量來(lái)獲取到線程的運(yùn)行結(jié)果。并且當(dāng)線程的執(zhí)行過(guò)程中拋出了異常通常來(lái)說(shuō)主線程也無(wú)法獲取到異常的信息的,只有通過(guò)ThreadFactory主動(dòng)設(shè)置線程的異常處理類才能感知到提交的線程中的異常信息。
- submit提交的方式有如下三種情況
2.1) <T> Future<T> submit(Callable<T> task);
這種提交的方式是提交一個(gè)實(shí)現(xiàn)了Callable接口的對(duì)象,可以看到Callable接口和Runnable接口的定義很類似,只不過(guò)Runnable接口中是一個(gè)沒(méi)有返回值的run方法,而Callable接口中是一個(gè)有返回值的call方法。
這種提交的方式會(huì)返回一個(gè)Future對(duì)象,這個(gè)Future對(duì)象代表這線程的執(zhí)行結(jié)果,當(dāng)主線程調(diào)用Future的get方法的時(shí)候會(huì)獲取到從線程中返回的結(jié)果數(shù)據(jù)。
如果在線程的執(zhí)行過(guò)程中發(fā)生了異常,get會(huì)獲取到異常的信息。
2.2) Future<?> submit(Runnable task);
也可以提交一個(gè)Runable接口的對(duì)象,這樣當(dāng)調(diào)用get方法的時(shí)候,如果線程執(zhí)行成功會(huì)直接返回null,如果線程執(zhí)行異常會(huì)返回異常的信息。
2.3) <T> Future<T> submit(Runnable task, T result);
當(dāng)線程正常結(jié)束的時(shí)候調(diào)用Future的get方法會(huì)返回result對(duì)象,當(dāng)線程拋出異常的時(shí)候會(huì)獲取到對(duì)應(yīng)的異常的信息。
18. 多線程并發(fā)控制中的倒計(jì)時(shí)器、循環(huán)柵欄是什么,有什么應(yīng)用場(chǎng)景?
- 倒計(jì)時(shí)器(CountDownLatch)
1.1) 實(shí)現(xiàn)最大的并行性:有時(shí)我們想同時(shí)啟動(dòng)多個(gè)線程,實(shí)現(xiàn)最大程度的并行性。例如,我們想測(cè)試一個(gè)單例類。如果我們創(chuàng)建一個(gè)初始計(jì)數(shù)為1的CountDownLatch,并讓所有線程都在這個(gè)鎖上等待,那么我們可以很輕松地完成測(cè)試。我們只需調(diào)用 一次countDown()方法就可以讓所有的等待線程同時(shí)恢復(fù)執(zhí)行。
1.2) 開(kāi)始執(zhí)行前等待n個(gè)線程完成各自任務(wù):例如應(yīng)用程序啟動(dòng)類要確保在處理用戶請(qǐng)求前,所有N個(gè)外部系統(tǒng)已經(jīng)啟動(dòng)和運(yùn)行了。
1.3) 死鎖檢測(cè):一個(gè)非常方便的使用場(chǎng)景是,你可以使用n個(gè)線程訪問(wèn)共享資源,在每次測(cè)試階段的線程數(shù)目是不同的,并嘗試產(chǎn)生死鎖。 - 循環(huán)柵欄(CyclicBarrier)
讓一組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí),屏障才會(huì)開(kāi)門(mén),所有被屏障攔截的線程才會(huì)繼續(xù)干活。
(1)CountDownLatch的計(jì)數(shù)器只能使用一次。而CyclicBarrier的計(jì)數(shù)器可以使用reset() 方法重置。所以CyclicBarrier能處理更為復(fù)雜的業(yè)務(wù)場(chǎng)景,比如如果計(jì)算發(fā)生錯(cuò)誤,可以重置計(jì)數(shù)器,并讓線程們重新執(zhí)行一次。
(2)CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得CyclicBarrier阻塞的線程數(shù)量。isBroken方法用來(lái)知道阻塞的線程是否被中斷。比如以下代碼執(zhí)行完之后會(huì)返回true。
(3)CountDownLatch會(huì)阻塞主線程,CyclicBarrier不會(huì)阻塞主線程,只會(huì)阻塞子線程。
19. 什么是活鎖、饑餓、無(wú)鎖、死鎖?
- 死鎖:
多個(gè)線程相互占用對(duì)方的資源的鎖,而又相互等對(duì)方釋放鎖,此時(shí)若無(wú)外力干預(yù),這些線程則一直處理阻塞的假死狀態(tài),形成死鎖。 - 活鎖:
拿到資源卻又相互釋放不執(zhí)行。當(dāng)多線程中出現(xiàn)了相互謙讓,都主動(dòng)將資源釋放給別的線程使用,這樣這個(gè)資源在多個(gè)線程之間跳動(dòng)而又得不到執(zhí)行。 - 饑餓:
3.1) 優(yōu)先級(jí)高的線程能夠插隊(duì)并優(yōu)先執(zhí)行,這樣如果優(yōu)先級(jí)高的線程一直搶占優(yōu)先級(jí)低線程的資源,導(dǎo)致低優(yōu)先級(jí)線程無(wú)法得到執(zhí)行。
3.2) 一個(gè)線程一直占著一個(gè)資源不放而導(dǎo)致其他線程得不到執(zhí)行,與死鎖不同的是饑餓在以后一段時(shí)間內(nèi)還是能夠得到執(zhí)行的,如那個(gè)占用資源的線程結(jié)束了并釋放了資源。 - 無(wú)鎖:
沒(méi)有對(duì)資源進(jìn)行鎖定,即所有的線程都能訪問(wèn)并修改同一個(gè)資源,但同時(shí)只有一個(gè)線程能修改成功。無(wú)鎖典型的特點(diǎn)就是一個(gè)修改操作在一個(gè)循環(huán)內(nèi)進(jìn)行,線程會(huì)不斷的嘗試修改共享資源,如果沒(méi)有沖突就修改成功并退出否則就會(huì)繼續(xù)下一次循環(huán)嘗試。所以,如果有多個(gè)線程修改同一個(gè)值必定會(huì)有一個(gè)線程能修改成功,而其他修改失敗的線程會(huì)不斷重試直到修改成功。
20. 什么是原子性、可見(jiàn)性、有序性?
- 原子性:
一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行。 - 可見(jiàn)性:
當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。
(volatile關(guān)鍵字、synchronized和Lock) - 有序性:
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
(volatile關(guān)鍵字、synchronized和Lock)
21. 什么是守護(hù)線程?有什么用?
也稱“服務(wù)線程”,在沒(méi)有用戶線程可服務(wù)時(shí)會(huì)自動(dòng)離開(kāi)。
為非后臺(tái)線程服務(wù)。
22. 怎么中斷一個(gè)線程?如何保證中斷業(yè)務(wù)不影響?
interrupt()
23. yield()方法有什么用?
- wait()和sleep()的關(guān)鍵的區(qū)別在于,wait()是用于線程間通信的,而sleep()是用于短時(shí)間暫停當(dāng)前線程。更加明顯的一個(gè)區(qū)別在于,當(dāng)一個(gè)線程調(diào)用wait()方法的時(shí)候,會(huì)釋放它鎖持有的對(duì)象的管程和鎖,但是調(diào)用sleep()方法的時(shí)候,不會(huì)釋放他所持有的管程。
-
yield和sleep的主要是,yield方法會(huì)臨時(shí)暫停當(dāng)前正在執(zhí)行的線程,來(lái)讓有同樣優(yōu)先級(jí)的正在等待的線程有機(jī)會(huì)執(zhí)行。如果沒(méi)有正在等待的線程,或者所有正在等待的線程的優(yōu)先級(jí)都比較低,那么該線程會(huì)繼續(xù)運(yùn)行。執(zhí)行了yield方法的線程什么時(shí)候會(huì)繼續(xù)運(yùn)行由線程調(diào)度器來(lái)決定,不同的廠商可能有不同的行為。yield方法不保證當(dāng)前的線程會(huì)暫停或者停止,但是可以保證當(dāng)前線程在調(diào)用yield方法時(shí)會(huì)放棄CPU。
thread.png
24. 什么是重入鎖,和Synchronized鎖有什么區(qū)別?
ReenTrantLock是一種自旋鎖,通過(guò)循環(huán)調(diào)用CAS操作來(lái)實(shí)現(xiàn)加鎖。它的性能比較好也是因?yàn)楸苊饬耸咕€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)。想盡辦法避免線程進(jìn)入內(nèi)核的阻塞狀態(tài)是我們?nèi)シ治龊屠斫怄i設(shè)計(jì)的關(guān)鍵鑰匙。
- ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。
- ReenTrantLock提供了一個(gè)Condition(條件)類,用來(lái)實(shí)現(xiàn)分組喚醒需要喚醒的線程們,而不是像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程。
- ReenTrantLock提供了一種能夠中斷等待鎖的線程的機(jī)制,通過(guò)lock.lockInterruptibly()來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。
25. Fork/Join框架是干什么的?
Fork/Join框架是一個(gè)比較特殊的線程池框架,專用于需要將一個(gè)任務(wù)不斷分解成多個(gè)子任務(wù)(分支),并將多個(gè)子任務(wù)的結(jié)果不斷進(jìn)行匯總得到最終結(jié)果(聚合)的并行計(jì)算框架。
Fork/Join框架適合能夠進(jìn)行拆分再合并的計(jì)算密集型(CPU密集型)任務(wù)。Fork/Join框架是一個(gè)并行框架,因此要求服務(wù)器擁有多CPU、多核,用以提高計(jì)算能力。
如果是單核、單CPU,不建議使用該框架,會(huì)帶來(lái)額外的性能開(kāi)銷,反而比單線程的執(zhí)行效率低。當(dāng)然不是因?yàn)椴⑿械娜蝿?wù)會(huì)進(jìn)行頻繁的線程切換,因?yàn)镕ork/Join框架在進(jìn)行線程池初始化的時(shí)候默認(rèn)線程數(shù)量為Runtime.getRuntime().availableProcessors(),單CPU單核的情況下只會(huì)產(chǎn)生一個(gè)線程,并不會(huì)造成線程切換,而是會(huì)增加Fork/Join框架的一些隊(duì)列、池化的開(kāi)銷。
26. 如何給線程傳遞參數(shù)?
- 通過(guò)構(gòu)造方法傳遞數(shù)據(jù)
- 通過(guò)變量和方法傳遞數(shù)據(jù)
- 通過(guò)回調(diào)函數(shù)傳遞數(shù)據(jù)
27. 線程安全的和不安全的集合
- 線程安全的集合對(duì)象:
Vector、HashTable、StringBuffer - 非線程安全的集合對(duì)象:
ArrayList 、LinkedList、HashMap、HashSet、TreeMap、TreeSet、StringBulider
28. 什么是CAS算法?在多線程中有哪些應(yīng)用。
樂(lè)觀鎖( Optimistic Locking)其實(shí)是一種思想。相對(duì)悲觀鎖而言,樂(lè)觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。
上面提到的樂(lè)觀鎖的概念中其實(shí)已經(jīng)闡述了他的具體實(shí)現(xiàn)細(xì)節(jié):主要就是兩個(gè)步驟:沖突檢測(cè)和數(shù)據(jù)更新。其實(shí)現(xiàn)方式有一種比較典型的就是Compare and Swap(CAS)。
CAS是項(xiàng)樂(lè)觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。