2021Java面試題合集-Java并發(fā)編程最全面試題123道_第1頁
2021Java面試題合集-Java并發(fā)編程最全面試題123道_第2頁
2021Java面試題合集-Java并發(fā)編程最全面試題123道_第3頁
2021Java面試題合集-Java并發(fā)編程最全面試題123道_第4頁
2021Java面試題合集-Java并發(fā)編程最全面試題123道_第5頁
已閱讀5頁,還剩35頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

基礎(chǔ)知識

1.為什么要使用并發(fā)編程

?提升多核CPU的利用率:一般來說一臺主機(jī)上的會有多個CPU核心,我們可以創(chuàng)建多個線程,理論

上講操作系統(tǒng)可以將多個線程分配給不同的CPU去執(zhí)行,每個CPU執(zhí)行一個線程,這樣就提高了

CPU的使用效率,如果使用單線程就只能有一個CPU核心被使用。

?比如當(dāng)我們在網(wǎng)上購物時,為了提升響應(yīng)速度,需要拆分,減庫存,生成訂單等等這些操作,就可

以進(jìn)行拆分利用多線程的技術(shù)完成。面對復(fù)雜業(yè)務(wù)模型,并行程序會比串行程序更適應(yīng)業(yè)務(wù)需求,

而并發(fā)編程更能吻合這種業(yè)務(wù)拆分。

?簡單來說就是:

。充分利用多核CPU的計算能力;

。方便進(jìn)行業(yè)務(wù)拆分,提升應(yīng)用性能

2.多線程應(yīng)用場景

?例如:迅雷多線程下載、數(shù)據(jù)庫連接池、分批發(fā)送短信等。

3.并發(fā)編程有什么缺點(diǎn)

?并發(fā)編程的目的就是為了能提高程序的執(zhí)行效率,提高程序運(yùn)行速度,但是并發(fā)編程并不總是能提

高程序運(yùn)行速度的,而且并發(fā)編程可能會遇到很多問題,比如:內(nèi)存泄漏、上下文切換、線程安

全、死鎖等問題。

4.并發(fā)編程三介必要因素是什么?

?原子性:原子,即一個不可再被分割的顆粒。原子性指的是一個或多個操作要么全部執(zhí)行成功要么

全部執(zhí)行失敗.

?可見性:一個線程對共享變量的修改,另一線程能夠立刻看到。(synchronized,volatile)

?有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。(處理器可能會對指令進(jìn)行重排序)

5.Java程序中怎么保證多線程的運(yùn)行安全?

?出現(xiàn)線程安全問題的原因一般都是三個原因:

。線程切換帶來的原子性問題解決辦法:使用多線程之間同步synchronized或使用鎖(lock)。

。緩存導(dǎo)致的可見性問題解決辦法:synchronized,volatile.LOCK,可以解決可見性問題

。編譯優(yōu)化帶來的有序性問題解決辦法:H叩pens-Before規(guī)則可以解決有序性問題

6.并行和并發(fā)有什么區(qū)別?

?并發(fā):多個任務(wù)在同一個CPU核上,按細(xì)分的時間片輪流(交替)執(zhí)行,從邏輯上來看那些任務(wù)是

同時執(zhí)行。

?并行:單位時間內(nèi),多個處理器或多核處理器同時處理多個任務(wù),是真正意義上的“同時進(jìn)行"。

?串行:有n個任務(wù),由一個線程按順序執(zhí)行。由于任務(wù)、方法都在一個線程執(zhí)行所以不存在線程不

安全情況,也就不存在臨界區(qū)的問題。

做一個形象的比喻:

?并發(fā)=倆個人用一臺電腦。

?并行=倆個人分配了倆臺電腦。

?串行=倆個人排隊(duì)使用一臺電腦。

7.什么是多線程

?多線程:多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運(yùn)行多個不同的線程來執(zhí)行

不同的任務(wù)。

8.多線程的好處

?可以提高CPU的利用率。在多線程程序中,一個線程必須等待的時候,CPU可以運(yùn)行其它的線程

而不是等待,這樣就大大提高了程序的效率。也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完

成各自的任務(wù).

9.多線程的劣勢:

?線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多;

?多線程需要協(xié)調(diào)和管理,所以需要CPU時間跟蹤線程;

?線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題。

10.線程和進(jìn)程區(qū)別

?什么是線程和進(jìn)程?

。進(jìn)程

一個在內(nèi)存中運(yùn)行的應(yīng)用程序。每個正在系統(tǒng)上運(yùn)行的程序都是一個進(jìn)程

?線程

進(jìn)程中的一個執(zhí)行任務(wù)(控制單元),它負(fù)責(zé)在程序里獨(dú)立執(zhí)行。

一個進(jìn)程至少有一個線程,?個進(jìn)程可以運(yùn)行多個線程,多個線程可共享數(shù)據(jù)。

?進(jìn)程與線程的區(qū)別

。根本區(qū)別:進(jìn)程是操作系統(tǒng)資源分配的基本單位,而線程是處理器任務(wù)調(diào)度和執(zhí)行的基本單

。資源開銷:每個進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(程序上下文),程序之間的切換會有較大

的開銷;線程可以看做輕量級的進(jìn)程,同一類線程共享代碼和數(shù)據(jù)空間,每個線程都有自己

獨(dú)立的運(yùn)行榭口程序計數(shù)器(PC),線程之間切換的開銷小。

。包含關(guān)系:如果一個進(jìn)程內(nèi)有多個線程,則執(zhí)行過程不是一條線的,而是多條線(線程)共

同完成的;線程是進(jìn)程的一部分,所以線程也被稱為輕權(quán)進(jìn)程或者輕量級進(jìn)程。

。內(nèi)存分配:同一進(jìn)程的線程共享本進(jìn)程的地址空間和資源,而進(jìn)程與進(jìn)程之間的地址空間和

資源是相互獨(dú)立的

。影響關(guān)系:一個進(jìn)程崩潰后,在保護(hù)模式下不會對其他進(jìn)程產(chǎn)生影響,但是一個線程崩潰有

可能導(dǎo)致整個進(jìn)程都死掉。所以多進(jìn)程要比多線程健壯。

。執(zhí)行過程:每個獨(dú)立的進(jìn)程有程序運(yùn)行的入口、順序執(zhí)行序列和程序出口。但是線程不能獨(dú)

立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制,兩者均可并發(fā)執(zhí)行

11.什么是上下文切換?

?多線程編程中一般線程的個數(shù)都大于CPU核心的個數(shù),而一個CPU核心在任意時刻只能被一個線

程使用,為了讓這些線程都能得到有效執(zhí)行,CPU采取的策略是為每個線程分配時間片并輪轉(zhuǎn)的

形式。當(dāng)一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使用,這個過程就屬于

一次上下文切換。

?概括來說就是:當(dāng)前任務(wù)在執(zhí)行完CPU時間片切換到另一個任務(wù)之前會先保存自己的狀態(tài),以便

下次再切換回這個任務(wù)時,可以再加載這個任務(wù)的狀態(tài)。任務(wù)從保存到再加載的過程就是一次上下

文切換。

?上下文切換通常是計算密集型的。也就是說,它需要相當(dāng)可觀的處理器時間,在每秒幾十上百次的

切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統(tǒng)來說意味著消耗大量的CPU

時間,事實(shí)上,可能是操作系統(tǒng)中時間消耗最大的操作。

?Linux相比與其他操作系統(tǒng)(包括其他類Unix系統(tǒng))有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文

切換和模式切換的時間消耗非常少。

12.守護(hù)線程和用戶線程有什么區(qū)別呢?

?用戶(User)線程:運(yùn)行在前臺,執(zhí)行具體的任務(wù),如程序的主線程、連接網(wǎng)絡(luò)的子線程等都是用

戶線程

?守護(hù)(Daemon)線程:運(yùn)行在后臺,為其他前臺線程^務(wù)。也可以說守護(hù)線程是JVM中非守護(hù)線

程的“傭人"。一旦所有用戶線程都結(jié)束運(yùn)行,守護(hù)線程會隨JVM一起結(jié)束工作

13.如何在Windows和Linux上查找哪個線程cpu利用率最高?

?windows上面用任務(wù)管理器看,linux下可以用top這個工具看。

。找出cpu耗用厲害的進(jìn)程pid,終端執(zhí)行top命令,然后按下shift+p(shi代+m是找出消耗內(nèi)存

最高)查找出cpu利用最厲害的pid號

根據(jù)上面第一^拿到的pid號,top-H-ppid。然后按下shift+p,查找出cpu利用率最厲害的

線程號,比如top-H-p1328

■將獲取到的線程號轉(zhuǎn)換成16進(jìn)制,去百度轉(zhuǎn)換一下就行

。使用jstack工具將進(jìn)程信息打印輸出,jstackpid號>/tmp/t.dat,比如jstack31365>

/tmp/t.dat

■編輯/tmp/t.dat文件,查找線程號對應(yīng)的信息

或者直接使用JDK自帶的工具查看“jconsole"、“visualvm",這都是JDK自帶的,可以直接在JDK的bin目錄

下找到直接使用

14.什么是線程死鎖

?死鎖是指兩個或兩個以上的進(jìn)程(線程)在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的

一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去.此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了

死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程(線程)稱為死鎖進(jìn)程(線程)。

?多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻

塞,因此程序不可能正常終止。

?如下圖所示,線程A持有資源2,線程B持有資源1,他們同時都想申請對方的資源,所以這兩個

線程就會互相等待而進(jìn)入死鎖狀態(tài)。

線程一線程二

15.形成死鎖的四介必要條件是什么

?互斥條件:在一段時間內(nèi)某資源只由一個進(jìn)程占用。如果此時還有其它進(jìn)程請求資源,就只能等

待,直至占有資源的進(jìn)程用畢釋放。

?占有且等待條件:指進(jìn)程已經(jīng)保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進(jìn)

程占有,此時請求進(jìn)程阻塞,但又對自己已獲得的其它資源保持不放。

?不可搶占條件:別人已經(jīng)占有了某項(xiàng)資源,你不能因?yàn)樽约阂残枰撡Y源,就去把別人的資源搶過

來。

?循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。(比如一個進(jìn)程集合,A在

等B,B在等C,C在等A)

16.如何避免線程死鎖

1.避免一個線程同時獲得多個鎖

2.避免一個線程在鎖內(nèi)同時占用多個資源,盡量保證每個鎖只占用一個資源

3.嘗試使用定時鎖,使用此1<.四1_。d<e1716。戊)來替代使用內(nèi)部鎖機(jī)制

17.創(chuàng)建線程的四種方式

?繼承Thread類;

publicclassMyThreadextendsThread{

?Override

publicvoidrun(){

System.out.println(Thread.currentThread().getName()+"run()方法正在執(zhí)

行…”);

?

?實(shí)現(xiàn)Runnable接口;

publicclassMyRunnableimplementsRunnable{

?Override

publicvoidrun(){

System.out.println(Thread.currentThread().getName()+"run()方法執(zhí)彳亍

中??.”);

)

?實(shí)現(xiàn)Callable接口;

publicclassMyCallableimplementsCal1able<lnteger>{

?Override

publicintegercall(){

System.out.println(Thread.currentThread().getName()+"call()方法執(zhí)彳亍

中…”);

return1;

)

?使用匿名內(nèi)部類方式

publicclassCreateRunnable{

publicstaticvoidmain(String[]args){

//創(chuàng)建多線程創(chuàng)建開始

Threadthread=newThread(newRunnable(){

publicvoidrun(){

for(inti=0;i<10;i++){

System.out.println("i:"+i);

)

)

});

thread.startf);

)

}

18.說一下runnable和callable有什么區(qū)別

相同點(diǎn):

?都是接口

?都可以編寫多線程程序

?都采用Thread.start。啟動線程

主要區(qū)別:

?Runnable接口run方法無返回值;Callable接口call方法有返回值,是個泛型,和Future、

FutureTask配合可以用來獲取異步執(zhí)行的結(jié)果

?Runnable接口run方法只能拋出運(yùn)行時異常,且無法捕獲處理;Callable接口call方法允許拋出

異常,可以獲取異常信息注:Callalbe接口支持返回執(zhí)行結(jié)果,需要調(diào)用FutureTask.getO得到,

此方法會阻塞主進(jìn)程的繼續(xù)往下執(zhí)行,如果不調(diào)用不會阻塞。

19.線程的run。和start。有什么區(qū)別?

?每個線程都是通過某個特定Thread對象所對應(yīng)的方法run()來完成其操作的,run()方法稱為線程

體。通過調(diào)用Thread類的start。方法來啟動一個線程。

?start。方法用于啟動線程,run()方法用于執(zhí)行線程的運(yùn)行時代碼。run()可以重復(fù)調(diào)用,而start。

只能調(diào)用一次。

?start。方法來啟動一個線程,真正實(shí)現(xiàn)了多線程運(yùn)行。調(diào)用start。方法無需等待run方法體代碼執(zhí)

行完畢,可以直接繼續(xù)執(zhí)行其他的代碼;此時線程是處于就緒狀態(tài),并沒有運(yùn)行。然后通過此

Thread類調(diào)用方法run()來完成其運(yùn)行狀態(tài),run()方法運(yùn)行結(jié)束,此線程終止。然后CPU再調(diào)度

其它線程。

?run()方法是在本線程里的,只是線程里的一個函數(shù),而不是多線程的。如果直接調(diào)用run(),其實(shí)

就相當(dāng)于是調(diào)用了一個普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下

面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)行時要使用

start。方法而不是run()方法。

20.為什么我們調(diào)用start()方法時會執(zhí)行run。方法,為什么我們不能直接調(diào)用

run()方法?

這是另一個非常經(jīng)典的java多線程面試問題,而且在面試中會經(jīng)常被問到。很簡單,但是很多人都會

答不上來!

?new一個Thread,線程進(jìn)入了新建狀態(tài)。調(diào)用start。方法,會啟動一個線程并使線程進(jìn)入了就緒

狀態(tài),當(dāng)分配到時間片后就可以開始運(yùn)行了。start。會執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動執(zhí)行

run()方法的內(nèi)容,這是真正的多線程工作。

?而直接執(zhí)行run()方法,會把run方法當(dāng)成—main線程下的普通方法去執(zhí)行,并不會在某個線

程中執(zhí)行它,所以這并不是多線程工作。

總結(jié):調(diào)用start方法方可啟動線程并使線程進(jìn)入就緒狀態(tài),而run方法只是thread的一個普通方法

調(diào)用,還是在主線程里執(zhí)行。

21.什么是Callable和Future?

?Callable接口類似于Runnable,從名字就可以看出來了,但是Runnable不會返回結(jié)果,并且無

法拋出返回結(jié)果的異常,而Callable功能更強(qiáng)大一些,被線程執(zhí)行后,可以返回值,這個返回值

可以被Future拿到,也就是說,F(xiàn)uture可以拿到異步執(zhí)行任務(wù)的返回值。

?Future接口表示異步任務(wù),是一可能還沒有完成的異步任務(wù)的結(jié)果.所以說Callable用于產(chǎn)生

結(jié)果,F(xiàn)uture用于獲取結(jié)果。

22.什么是FutureTask

?FutureTask表示一個異步運(yùn)算的任務(wù)。FutureTask里面可以傳入一個Capable的具體實(shí)現(xiàn)類,可

以對這個異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等待獲取、判斷是否已經(jīng)完成、取消任務(wù)等操作。只有當(dāng)運(yùn)算

完成的時候結(jié)果才能取回,如果運(yùn)算尚未完成get方法將會阻塞。一個FutureTask對象可以對調(diào)

用了Callable和Runnable的對象進(jìn)行包裝,由于FutureTask也是Runnable接口的實(shí)現(xiàn)類,所

以FutureTask也可以放入線程池中。

23.線程的狀態(tài)

被其他線程喚醒:

不會釋放鎖

?新建(new):新創(chuàng)建了一個線程對象。

?就緒(可運(yùn)行狀態(tài))(runnable):線程對象創(chuàng)建后,當(dāng)調(diào)用線程對象的start。方法,該線程處于就

緒狀態(tài),等待被線程調(diào)度選中,獲取cpu的使用權(quán)。

?運(yùn)行(running):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu時間片(timeslice),執(zhí)行程序代碼。

注:就緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,也就是說,線程要想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處

于就緒狀態(tài)中;

?阻塞(block):處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時放棄對CPU的使用權(quán),停止執(zhí)行,止匕時

進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才有機(jī)會再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。

。阻塞的情況分三種:

。(一).等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait。方法,JVM會把該線程放入等待隊(duì)列(waitting

queue)中,使本線程進(jìn)入到等待阻塞狀態(tài);

。(二).同步阻塞:線程在獲取synchronized同步鎖失敗(因?yàn)殒i被其它線程所占用),,貝!JJVM

會把該線程放入鎖池(lockpool)*,線程會進(jìn)入同步阻塞狀態(tài);

。(三).其他阻塞:通過調(diào)用線程的sleep?;騤oin?;虬l(fā)出了I/O請求時,線程會進(jìn)入到阻塞狀

態(tài)。當(dāng)sleep。狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入

就緒狀態(tài)。

?死亡(dead)(結(jié)束):線程run()、main。方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束

生命周期。死亡的線程不可再次復(fù)生。

24.Java中用到的線程調(diào)度算法是什么?

?計算機(jī)通常只有一個CPU,在任意時刻只能執(zhí)行一條機(jī)器指令,每個線程只有獲得CPU的使用權(quán)

才能執(zhí)行指令。所謂多線程的并發(fā)運(yùn)行,其實(shí)是指從宏觀上看,各個線程輪流獲得CPU的使用

權(quán),分別執(zhí)行各自的任務(wù)。在運(yùn)行池中,會有多個處于就緒狀態(tài)的線程在等待CPU,JAVA虛擬機(jī)

的一項(xiàng)任務(wù)就是負(fù)責(zé)線程的調(diào)度,線程調(diào)度是指按照特定機(jī)制為多個線程分配CPU的使用權(quán)。

(Java是由JVM中的線程計數(shù)器來實(shí)現(xiàn)線程調(diào)度)

?有兩種調(diào)度模型:分時調(diào)度模型和搶占式調(diào)度模型。

。分時調(diào)度模型是指讓所有的線程輪流獲得cpu的使用權(quán),并且平均分配每個線程占用的CPU

的時間片這個也比較好理解。

。Java虛擬機(jī)采用搶占式調(diào)度模型,是指優(yōu)先讓可運(yùn)行池中優(yōu)先級高的線程占用CPU,如果可

運(yùn)行池中的線程優(yōu)先級相同,那么就隨機(jī)選擇一個線程,使其占用CPU。處于運(yùn)行狀態(tài)的線

程會一直運(yùn)行,直至它不得不放棄CPU。

25.線程的調(diào)度策略

線程調(diào)度器選擇優(yōu)先級最高的線程運(yùn)行,但是,如果發(fā)生以下情況,就會終止線程的運(yùn)行:

?(1)線程體中調(diào)用了yield方法讓出了對cpu的占用權(quán)利

?(2)線程體中調(diào)用了sleep方法使線程進(jìn)入睡眠狀態(tài)

?(3)線程由于I0操作受到阻塞

?(4)另夕一個更高優(yōu)先級線程出現(xiàn)

?(5)在支持時間片的系統(tǒng)中,該線程的時間片用完

26.什么是線程調(diào)度器(ThreadScheduler)和時間分片(TimeSlicing)?

?線程調(diào)度器是一個操作系統(tǒng)服務(wù),它負(fù)責(zé)為Runnable狀態(tài)的線程分配CPU時間。一旦我們創(chuàng)建

一個線程并啟動它,它的執(zhí)行便依賴于線程調(diào)度器的實(shí)現(xiàn).

?時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基于線

程優(yōu)先級或者線程等待的時間。

?線程調(diào)度并不受到Java虛擬機(jī)控制,所以由應(yīng)用程序來控制它是更好的選擇(也就是說不要讓你

的程序依賴于線程的優(yōu)先級)。

27.請說出與線程同步以及線程調(diào)度相關(guān)的方法。

?(Dwait():使一個線程處于等待(阻塞)狀態(tài),并且釋放所持有的對象的鎖;

?(2)sleep。:使一個正在運(yùn)行的線程處于睡眠狀態(tài),是一個靜態(tài)方法,調(diào)用此方法要處理

InterruptedException異常;

?(3)notify。:喚醒一個處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時候,并不能確切的喚醒某一

個等待狀態(tài)的線程,而是由JVM確定喚醒哪個線程,而且與優(yōu)先級無關(guān);

?(4)notityAIIO:喚醒所有處于等待狀態(tài)的線程,該方法并不是將對象的鎖給所有線程,而是讓它

們競爭,只有獲得鎖的線程才能進(jìn)入就緒狀態(tài);

28.sleep()和wait()有什么區(qū)別?

兩者都可以暫停線程的執(zhí)行

?類的不同:sleep。是Thread線程類的靜態(tài)方法,wait。是Object類的方法。

?是否釋放鎖:sleep。不釋放鎖;wait。釋放鎖。

?用途不同:Wait通常被用于線程間交互/通信,sleep通常被用于暫停執(zhí)行。

?用法不同:wait()方法被調(diào)用后,線程不會自動蘇醒,需要別的線程調(diào)用同一個對象上的notify。

或者notifyAII()方法。sleep。方法執(zhí)行完成后,線程會自動蘇醒?;蛘呖梢允褂脀ait(long

timeout)超時后線程會自動蘇醒。

29.你是如何調(diào)用wait。方法的?使用if塊還是循環(huán)?為什么?

?處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒,如果不在循環(huán)中檢杳等待條件,程序就會在沒

有滿足結(jié)束條件的情況下退出。

?wait。方法應(yīng)該在循環(huán)調(diào)用,因?yàn)楫?dāng)線程獲取到CPU開始執(zhí)行的時候,其他條件可能還沒有滿

足,所以在處理前,循環(huán)檢測條件是否滿足會更好。下面是一段標(biāo)準(zhǔn)的使用wait和notify方法的

代碼:

synchronized(monitor){

//判斷條件謂詞是否得到滿足

while(!locked){

//等待喚醒

monitor;

)

//處理其他的業(yè)務(wù)邏輯

30.為什么線程通信的方法wait(),notify。和notifyAII。被定義在Object類里?

?因?yàn)镴ava所有類的都繼承了Object,Java想讓任何對象都可以作為鎖,并且wait(),notify。等方法

用于等待對象的鎖或者喚醒線程,在Java的線程中并沒有可供任何對象使用的鎖,所以任意對象

調(diào)用方法一定定義在Object類中。

?有的人會說,既然是線程放棄對象鎖,那也可以把wait。定義在Thread類里面啊,新定義的線程繼

承于Thread類,也不需要重新定義wait()方法的實(shí)現(xiàn)。然而,這樣做有一個非常大的問題,一個線

程完全可以持有很多鎖,你一個線程放棄鎖的時候,到底要放棄哪個鎖?當(dāng)然了,這種設(shè)計并不是

不能實(shí)現(xiàn),只是管理起來更加復(fù)雜。

31.為什么wait。,notify。和notifyAH。必須在同步方法或者同步塊中被調(diào)用?

?當(dāng)一個線程需要調(diào)用對象的wait。方法的時候,這個線程必須擁有該對象的鎖,接著它就會釋放這

個對象鎖并進(jìn)入等待狀態(tài)直到其他線程調(diào)用這個對象上的notify。方法。同樣的,當(dāng)一個線程需要

調(diào)用對象的notify。方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可以得到這個對象

鎖。由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過同步來實(shí)現(xiàn),所以他們只能在

同步方法或者同步塊中被調(diào)用。

32.Thread類中的yield方法有什么作用?

?使當(dāng)前線程從執(zhí)行狀態(tài)(運(yùn)行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài)).

?當(dāng)前線程到了就緒狀態(tài),那么接下來哪個線程會從就緒狀態(tài)變成執(zhí)行狀態(tài)呢?可能是當(dāng)前線程,也

可能是其他線程,看系統(tǒng)的分配了。

33.為什么Thread類的sleep。和yield()方法是靜態(tài)的?

?Thread類的sleep。和yield。方法將在當(dāng)前正在執(zhí)行的線程上運(yùn)行。所以在其他處于等待狀態(tài)的線

程上調(diào)用這些方法是沒有意義的。這就是為什么這些方法是靜態(tài)的。它們可以在當(dāng)前正在執(zhí)行的線

程中工作,并避免程序員錯誤的認(rèn)為可以在其他^運(yùn)行線程調(diào)用這些方法。

34.線程的sleep。方法和yield。方法有什么區(qū)別?

?(Dsleep。方法給其他線程運(yùn)行機(jī)會時不考慮線程的優(yōu)先級,因此會給低優(yōu)先級的線程以運(yùn)行的

機(jī)會;yield。方法只會給相同優(yōu)先級或更高優(yōu)先級的線程以運(yùn)行的機(jī)會;

?(2)線程執(zhí)行sleep。方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行yield。方法后轉(zhuǎn)入就緒

(ready)狀態(tài);

?(3)sleep。方法聲明拋出InterruptedException,而yield。方法沒有聲明任何異常;

?(4)sleep。方法比yield。方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性,通常不建議

使用yield。方法來控制并發(fā)線程的執(zhí)行。

35.如何停止一個正在運(yùn)行的線程?

?在java中有以下3種方法可以終止正在運(yùn)行的線程:

。使用退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止。

。使用Stop方法強(qiáng)行終止,但是不推薦這個方法,因?yàn)閟top和suspend及resume一樣都是過期

作廢的方法。

。使用interrupt方法中斷線程。

36.Java中interrupted和islnterrupted方法的區(qū)別?

?interrupt:用于中斷線程。調(diào)用該方法的線程的狀態(tài)為將被置為"中斷"狀態(tài)。

注意:線程中斷僅僅是置線程的中斷狀態(tài)位,不會停止線程。需要用戶自己去監(jiān)視線程的狀態(tài)為并做處

理。支持線程中斷的方法他就是線程中斷后會拋出interruptedException的方法)就是在監(jiān)視線程的

中斷狀態(tài),一旦線程的中斷狀態(tài)被置為"中斷狀態(tài)",就會拋出中斷異常.

?interrupted:是靜態(tài)方法,查看當(dāng)前中斷信號是true還是faIse并且清除中斷信號。如果一個線程

被中斷了,第一次調(diào)用interrupted則返回true,第二次和后面的就返回false了。

?islnterrupted:是可以返回當(dāng)前中斷信號是true還是false,與interrupt最大的差別

37.什么是阻塞式方法?

?阻塞式方法是指程序會一直等待該方法完成期間不做其他事情,Serversocket的accept。方法就是

一直等待客戶端連接.這里的阻塞是指調(diào)用結(jié)果返回之前,當(dāng)前線程會被掛起,直到得到結(jié)果之后

才會返回。此外,還有異步和非阻塞式方法在任務(wù)完成前就返回。

38.Java中你怎樣喚醒一個阻塞的線程?

?首先,wait。、notify。方法是針對對象的,調(diào)用任意對象的wait。方法都將導(dǎo)致線程阻塞,阻塞

的同時也將釋放該對象的鎖,相應(yīng)地,調(diào)用任意對象的notify。方法則將隨機(jī)解除該對象阻塞的線

程,但它需要重新獲取該對象的鎖,直到獲取成功才能往下執(zhí)行;

?其次,wait、notify方法必須在synchronized塊或方法中被調(diào)用,并且要保證同步塊或方法的鎖

對象與調(diào)用wait、notify方法的對象是同一個,如此一來在調(diào)用wait之前當(dāng)前線程就已經(jīng)成功獲

取某對象的鎖,執(zhí)行wait阻塞后當(dāng)前線程就將之前獲取的對象鎖釋放。

39.notifyO和notifyAIK)有什么區(qū)別?

?如果線程調(diào)用了對象的wait()方法,那么線程便會處于該對象的等待池中,等待池中的線程不會去

競爭該對象的鎖。

?notifyAIK)會喚醒所有的線程,notify。只會喚醒一個線程。

?notifyAIK)調(diào)用后,會將全部線程由等待池移到鎖池,然后參與鎖的競爭,競爭成功則繼續(xù)執(zhí)行,

如果不成功則留在鎖池等待鎖被釋放后再次參與競爭。而notify。只會喚醒一個線程,具體喚醒哪

一個線程由虛擬機(jī)控制。

40.如何在兩個線程間共享數(shù)據(jù)?

?在兩個線程間共享變量即可實(shí)現(xiàn)共享。

一般來說,共享變量要求變量本身是線程安全的,然后在線程內(nèi)使用的時候,如果有對共享變量的復(fù)合操作,那么

也得保證復(fù)合操作的線程安全性。

41.Java如何實(shí)現(xiàn)多線程之間的通訊和協(xié)作?

?可以通過中斷和共享變量的方式實(shí)現(xiàn)線程間的通訊和I協(xié)作

?比如說最經(jīng)典的生產(chǎn)者-消費(fèi)者模型:當(dāng)隊(duì)列滿時,生產(chǎn)者需要等待隊(duì)列有空間才能繼續(xù)往里面放

入商品,而在等待的期間內(nèi),生產(chǎn)者必須釋放對臨界資源(即隊(duì)列)的占用權(quán).因?yàn)樯a(chǎn)者如果不

釋放對臨界資源的占用權(quán),那么消費(fèi)者就無法消費(fèi)隊(duì)列中的商品,就不會讓隊(duì)列有空間,那么生產(chǎn)

者就會一直無限等待下去。因此,一般情況下,當(dāng)隊(duì)列滿時,會讓生產(chǎn)者交出對臨界資源的占用

權(quán),并進(jìn)入掛起狀態(tài).然后等待消費(fèi)者消費(fèi)了商品,然后消費(fèi)者通知生產(chǎn)者隊(duì)列有空間了。同樣

地,當(dāng)隊(duì)列空時,消費(fèi)者也必須等待,等待生產(chǎn)者通知它隊(duì)列中有商品了。這種互相通信的過程就

是線程間的協(xié)作。

?Java中線程通信協(xié)作的最常見方式:

o—.syncrhoized加鎖的線程的Object類的wait()/notify()/notifyAII()

■二.ReentrantLock類力口鎖的線程的Condition類的await()/signal()/signalAII()

?線程間直接的數(shù)據(jù)交換:

。三.通過管道進(jìn)行線程間通信:字節(jié)流、字符流

42.同步方法和同步塊,哪個是更好的選擇?

?同步塊是更好的選擇,因?yàn)樗粫i住整個對象(當(dāng)然你也可以讓它鎖住整個對象).同步方法會

鎖住整個對象,哪怕這個類中有多個不相關(guān)聯(lián)的同步塊,這通常會導(dǎo)致他們停止執(zhí)行并需要等待獲

得這個對象上的鎖。

?同步塊更要符合開放調(diào)用的原則,只在需要鎖住的代碼塊鎖住相應(yīng)的對象,這樣從側(cè)面來說也可以

避免死鎖。

請知道一條原則:同步的范圍越小越好。

43.什么是線程同步和線程互斥,有哪幾種實(shí)現(xiàn)方式?

?當(dāng)一個線程對共享的數(shù)據(jù)進(jìn)行操作時,應(yīng)使之成為一個"原子操作",即在沒有完成相關(guān)操作之前,

不允許其他線程打斷它,否則,就會破壞數(shù)據(jù)的完整性,必然會得到錯誤的處理結(jié)果,這就是線程

的同步。

?在多線程應(yīng)用中,考慮不同線程之間的數(shù)據(jù)同步和防止死鎖。當(dāng)兩個或多個線程之間同時等待對方

釋放資源的時候就會形成線程之間的死鎖。為了防止死鎖的發(fā)生,需要通過同步來實(shí)現(xiàn)線程安全。

?線程互斥是指對于共享的進(jìn)程系統(tǒng)資源,在各單個線程訪問時的排它性。當(dāng)有若干個線程都要使用

某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到

占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。

?線程間的同步方法大體可分為兩類:用戶模式和內(nèi)核模式。顧名思義,內(nèi)核模式就是指利用系統(tǒng)內(nèi)

核對象的單一性來進(jìn)行同步,使用時需要切換內(nèi)核態(tài)與用戶態(tài),而用戶模式就是不需要切換到內(nèi)核

態(tài),只在用戶態(tài)完成操作。

?用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區(qū)。內(nèi)核模式下的方法有:事

件,信號量,互斥量。

?實(shí)現(xiàn)線程同步的方法

。同步代碼方法:sychronized關(guān)鍵字修飾的方法

。同步代碼塊:sychronized關(guān)鍵字修飾的代碼塊

。使用特殊變量域volatile實(shí)現(xiàn)線程同步:volatile關(guān)犍字為域變量的訪問提供了一種免鎖機(jī)制

。使用重入鎖實(shí)現(xiàn)線程同步:reentrantlock類是可沖入、互斥、實(shí)現(xiàn)了lock接口的鎖他與

sychronized方法具有相同的基本行為和語義

44.在監(jiān)視器(Monitor)內(nèi)部,是如何做線程同步的?程序應(yīng)該做哪種級別的同

步?

?在java虛擬機(jī)中,監(jiān)視器和鎖在Java虛擬機(jī)中是一塊使用的。監(jiān)視器監(jiān)視一塊同步代碼塊,確保

一次只有一個線程執(zhí)行同步代碼塊。每一個監(jiān)視器都和一個對象引用相關(guān)聯(lián)。線程在獲取鎖之前不

允許執(zhí)行同步代碼。

?一旦方法或者代碼塊被synchronized修飾,那么這個部分就放入了監(jiān)視器的監(jiān)視區(qū)域,確保一次

只能有一個線程執(zhí)行該部分的代碼,線程在獲取鎖之前不允許執(zhí)行該部分的代碼

?另外java還提供了顯式監(jiān)視器(Lock)和隱式監(jiān)視器(synchronized)兩種鎖方案

45.如果你提交任務(wù)時,線程池隊(duì)列已滿,這時會發(fā)生什么

?有倆種可能:

(1)如果使用的是無界隊(duì)歹ULinkedBlockingQueue,也就是無界隊(duì)列的話,沒關(guān)系,繼續(xù)添加任務(wù)到

阻塞隊(duì)列中等待執(zhí)行,因?yàn)長inkedBlockingQueue可以近乎認(rèn)為是一個無窮大的隊(duì)列,可以無限存放

彳鎊

(2)如果使用的是有界隊(duì)列比如ArrayBlockingQueue,任務(wù)首先會被添加到ArrayBlockingQueue

中,ArrayBlockingQueue滿了,會根據(jù)maximumPoolSize的值增加線程數(shù)量,如果增加了線程數(shù)量

還是處理不過來,ArrayBlockingQueue繼續(xù)滿,那么則會使用拒絕策略RejectedExecutionHandler

處理滿了的任務(wù),默認(rèn)是AbortPolicy

46.什么叫線程安全?servlet是線程安全嗎?

?線程安全是編程中的術(shù)語,指某個方法在多線程環(huán)境中被調(diào)用時,能夠正確地處理多個線程之間的

共享變量,使程序功能正確完成。

?Servlet不是線程安全的,servlet是單實(shí)例多線程的,當(dāng)多個線程同時訪問同f方法,是不能保

證共享變量的線程安全性的。

?Struts2的action是多實(shí)例多線程的,是線程安全的,每個請求過來都會new一個新的action分

配給這個請求,請求完成后銷毀。

?SpringMVC的Controller是線程安全的嗎?不是的,和Servlet類似的處理流程。

?Struts2好處是不用考慮線程安全問題;Servlet和SpringMVC需要考慮線程安全問題,但是性能

可以提升不用處理太多的gc,可以使用ThreadLocal來處理多線程的問題。

47.在Java程序中怎么保證多線程的運(yùn)行安全?

?方法一:使用安全類,比如java.util.concurrent下的類,使用原子類Atomiclnteger

?方法二:使用自動鎖synchronized。

?方法三:使用手動鎖Lock。

?手動鎖Java示例代碼如下:

Locklock=newReentrantLock();

lock.lock();

try{

System,out.printing獲得鎖”);

}catch(Exceptione){

//TODO:handleexception

}finally{

System.out.printin(“釋放鎖”);

lock.unlock();

)

48.你對線程優(yōu)先級的理解是什么?

?每一個線程都是有優(yōu)先級的,一般來說,高優(yōu)先級的線程在運(yùn)行時會具有優(yōu)先權(quán),但這依賴于線程

調(diào)度的實(shí)現(xiàn),這個實(shí)現(xiàn)是和操作系統(tǒng)相關(guān)的(OSdependent)。我們可以定義線程的優(yōu)先級,但是

這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行。線程優(yōu)先級是一個int變量(從1-10),

1代表最低優(yōu)先級,10代表最高優(yōu)先級。

?Java的線程優(yōu)先級調(diào)度會委托給操作系統(tǒng)去處理,所以與具體的操作系統(tǒng)優(yōu)先級有關(guān),如非特別

需要,一般無需設(shè)置線程優(yōu)先級。

?當(dāng)然,如果你真的想設(shè)置優(yōu)先級可以通過setPriority()方法設(shè)置,但是設(shè)置了不一定會該變,這個

是不準(zhǔn)確的

49.線程類的構(gòu)造方法、靜態(tài)塊是被哪個線程調(diào)用的

?這是一個非常刁鉆和狡猾的問題。請記?。壕€程類的構(gòu)造方法、靜態(tài)塊是被new這個線程類所在

的線程所調(diào)用的,而run方法里面的代碼才是被線程自身所調(diào)用的。

?如果說上面的說法讓你感到困惑,那么我舉個例子,假設(shè)Thread2中new了Threadl,main函

數(shù)中new了Thread2,那么:

(1)Thread2的構(gòu)造方法、靜態(tài)塊是main線程調(diào)用的,Thread2的run。方法是Thread2自己調(diào)用的

(2)Thread1的構(gòu)造方法、靜態(tài)塊是Thread2調(diào)用的,Thread1的run。方法是ThreacH自己調(diào)用的

50.Java中怎么獲取一份線程dump文件?你如何在Java中獲取線程堆棧?

?Dump文件是進(jìn)程的內(nèi)存鏡像??梢园殉绦虻膱?zhí)行狀態(tài)通過調(diào)試器保存到dump文件中。

?在Linux下,你可以通過命令kill-3PID(Java進(jìn)程的進(jìn)程ID)來獲取Java應(yīng)用的dump文件。

?在Windows下,你可以按下Ctrl+Break來獲取。這樣JVM就會將線程的dump文件打E倒標(biāo)

準(zhǔn)輸出或錯誤文件中,它可能打印在控制臺或者日志文件中,具體位置依賴應(yīng)用的配置。

51.一個線程運(yùn)行時發(fā)生異常會怎樣?

?如果異常沒有被捕獲該線程將會停止執(zhí)行。Thread.UncaughtExceptionHandler是用于處理未捕

獲異常造成線程突然中斷情況的一個內(nèi)嵌接口。當(dāng)一個未捕獲異常將造成線程中斷的時候,JVM

會使用Thread.getUncaughtExceptionHandler。來查詢線程的UncaughtExceptionHandler并將

線程和異常作為參數(shù)傳遞給handler的uncaughtException()方法進(jìn)行處理。

52.Java線程數(shù)過多會造成什么異常?

?線程的生命周期開銷非常高

?消耗過多的CPU

資源如果可運(yùn)行的線程數(shù)量多于可用處理器的數(shù)量,那么有線程將會被閑置。大量空閑的線程會占

用許多內(nèi)存,給垃圾回收器帶來壓力,而且大量的線程在競爭CPU資源時還將產(chǎn)生其他性能的開

銷。

?降低穩(wěn)定'由VM

在可創(chuàng)建線程的數(shù)量上存在一個限制,這個限制值將隨著平臺的不同而不同,并且承受著多個因素

制約,包括JVM的啟動參數(shù)、Thread構(gòu)造函數(shù)中請求棧的大小,以及底層操作系統(tǒng)對線程的限制

等。如果破壞了這些限制,那么可能拋出。戊0£1\/16巾。》£汗。「異常。

53.多線程的常用方法

方法名描述

sleepO強(qiáng)迫一個線程睡眠N毫秒

isAlive()判哈個編魄否存活。

join()等待線程終止。

activeCount()程序中活躍的線程數(shù)。

enumerateO枚舉程序中的線程.

currentThreadO得到當(dāng)前線程。

isDaemonO一個線程是否為守護(hù)線程.

setDaemon()設(shè)置一個線程為守護(hù)線程。

setName()為線程設(shè)置一個名稱。

wait()強(qiáng)迫一個線程等待。

notifyO通知一個線程繼續(xù)運(yùn)行。

setPriorityO設(shè)置一個線程的優(yōu)先級。

并發(fā)理論

1.Java中垃圾回收有什么目的?什么時候進(jìn)行垃圾回收?

?垃圾回收是在內(nèi)存中存在沒有引用的對象或超過作用域的對象時進(jìn)行的.

?垃圾回收的目的是識別并且丟棄應(yīng)用不再使用的對象來釋放和重用資源。

2.線程之間如何通信及線程之間如何同步

?在并發(fā)編程中,我們需要處理兩個關(guān)鍵問題:線程之間如何通信及線程之間如何同步。通信是指線

程之間以如何來交換信息。一般線程之間的通信機(jī)制有兩種:共享內(nèi)存和消息傳遞。

?Java的并發(fā)采用的是共享內(nèi)存模型,Java線程之間的通信總是隱式進(jìn)行,整個通信過程對程序員完

全透明。如果編寫多線程程序的Java程序員不理解隱式進(jìn)行的線程之間通信的工作機(jī)制,很可能會

遇到各種奇怪的內(nèi)存可見性問題。

3.Java內(nèi)存模型

?共享內(nèi)存模型指的就是Java內(nèi)存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入時,能對另一

個線程可見。從抽象的角度來看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變

量存儲在主內(nèi)存(mainmemory)中,每個線程都有一個私有的本地內(nèi)存(localmemory),本

地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念,并不真實(shí)存

在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。

?從上圖來看,線程A與線程B之間如要通信的話,必須要經(jīng)歷下面2個步驟:

1.首先,線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去。

2.然后,線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量。

下面通過示意圖來說明線程之間的通信

主內(nèi)存

:c-l

?總結(jié):什么是Java內(nèi)存模型:java內(nèi)存模型簡稱jmm,定義了一個線程對另一個線程可見。共享變

量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存,當(dāng)多個線程同時訪問一個數(shù)據(jù)的時候,可能本

地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題。

4.如果對象的引用被置為null,垃圾收集器是否會立即釋放對象占用的內(nèi)存?

?不會,在下一個垃圾回調(diào)周期中,這個對象將是被可回收的。

?也就是說并不會立即被垃圾收集器立刻回收,而是在下一次垃圾回收時才會釋放其占用的內(nèi)存。

5.finalize。方法什么時候被調(diào)用?析構(gòu)函數(shù)(finalization)的目的是什么?

?1.垃圾回收器(garbagecolector)決定回收某對象時,就會運(yùn)行該對象的finalize。方法;

finalize是Object類的一?"方法,該方法在Object類中的聲明protectedvoidfinalize()throws

Throwable{)在垃圾回收器執(zhí)行時會調(diào)用被回收對象的finalize。方法,可以覆蓋此方法來實(shí)現(xiàn)對

其資源的回收。注意:一旦垃圾回收器準(zhǔn)備釋放對象占用的內(nèi)存,將首先調(diào)用該對象的finalize。方

法,并且下一次垃圾回收動作發(fā)生時,才真正回收對象占用的內(nèi)存空間

?1.GC本來就是內(nèi)存回收了,應(yīng)用還需要在finalization做什么呢?答案是大部分時候,什么都

不用做(也就是不需要重載)。只有在某些很特殊的情況下,比如你調(diào)用了一些native的方法

(一般是C寫的),可以要在finaliztion里去調(diào)用C的釋放函數(shù)。

。Finalizetion主要用來釋放被對象占用的資源(不是指內(nèi)存,而是指其他資源,比如文件(File

Handle),端口(ports)、數(shù)據(jù)庫連接(DBConnection)等)。然而,它不能真正有效地工作。

6.什么是重排序

?程序執(zhí)行的順序按照代碼的先后I放序執(zhí)行。

?一般來說處理器為了提高程序運(yùn)行效率,可能會對輸入代碼進(jìn)行優(yōu)化,進(jìn)行重新排序(重排序),

它不保證程序中各個語句的執(zhí)行先后順序同代碼中的W頁序一致,但是它會保證程序最終執(zhí)行結(jié)果和

代碼順序執(zhí)行的結(jié)果是一致的。

inta=5//語句1

intr=3//語句2

a=a+2//語句3

r=a*a;//語句4

?則因?yàn)橹嘏判?,他還可能執(zhí)行順序?yàn)椋ㄟ@里標(biāo)注的是語句的執(zhí)行順序)2-1-3-4,1-3-2-4但絕不

可能2-1-4-3,因?yàn)檫@打破了依賴關(guān)系。

?顯然重排序?qū)尉€程運(yùn)行是不會有任何問題,但是多線程就不一定了,所以我們在多線程編程時就

得考慮這個問題了。

7.重排序?qū)嶋H執(zhí)行的指令步驟

1.編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

2.指令級并行的重排序。現(xiàn)代處理器采用了指令級并行技術(shù)(ILP)來將多條指令重疊執(zhí)行。如果不

存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行質(zhì)序.

3.內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在

亂序執(zhí)行.

?這些重排序?qū)τ趩尉€程沒問題,但是多線程都可能會導(dǎo)致多線程程序出現(xiàn)內(nèi)存可見性問題。

8.重排序遵守的規(guī)則

?as-if-serial:

1.不管怎么排序,結(jié)果不能改變

2.不存在數(shù)據(jù)依賴的可以被編譯器和處理器重排序

3.一個操作依賴兩個操作,這兩個操作如果不存在依賴可以重排序

4.單線程根據(jù)此規(guī)則不會有問題,但是重排序后多線程會有問題

9.as-if-serial規(guī)則和happens-before規(guī)則的區(qū)別

?as-if-serial語義保證單線程內(nèi)程序的執(zhí)行結(jié)果不被改變,h叩pens-before關(guān)系保證正確同步的多

線程程序的執(zhí)行結(jié)果不被改變。

?as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個幻境:單線程程序是按程序的J1I頁序來執(zhí)行

的。happens-before關(guān)系給編寫正確同步的多線程程序的程序員創(chuàng)造了一個幻境:正確同步的多

線程程序是按happens-before指定的順序來執(zhí)行的.

?as-if-serial語義和happens-before這么做的目的,都是為了在不改變程序執(zhí)行結(jié)果的前提下,盡

可能地提高程序執(zhí)行的并行度。

10.并發(fā)關(guān)鍵字synchronized?

?在Java中,synchronized關(guān)鍵字是用來控制線程同步的,就是在多線程的環(huán)境下,控制

synchronized代碼段不被多個線程同時執(zhí)行。synchronized可以修飾類、方法、變量。

?另外,在Java早期版本中,synchronized屬于重量級鎖,效率低下,因?yàn)楸O(jiān)視器鎖(monitor)

是依賴于底層的操作系統(tǒng)的MutexLock來實(shí)現(xiàn)的,Java的線程是映射到操作系統(tǒng)的原生線程之上

的。如果要掛起或者喚醒一個線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時

需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,時間成本相對較高,這

也是為什么早期的synchronized效率低的原因。慶幸的是在Java6之后Java官方對從JVM層面

對synchronized較大優(yōu)化,所以現(xiàn)在的synchronized鎖效率也優(yōu)化得很不錯了。JDK1.6對鎖的

實(shí)現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術(shù)來

減少鎖操作的開銷。

11.說說自己是怎么使用synchronized關(guān)鍵字,在項(xiàng)目中用到了嗎

synchronized關(guān)鍵字最主要的三種使用方式:

?修飾實(shí)例方法:作用于當(dāng)前對象實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前對象實(shí)例的鎖

?修飾靜態(tài)方法:也就是給當(dāng)前類加鎖,會作用于類的所有對象實(shí)例,因?yàn)殪o態(tài)成員不屬于任何一個

實(shí)例對象,是類成員(static表明這是該類的一個靜態(tài)資源,不管new了多少個對象,只有一

份)。所以如果f線程A調(diào)用一個實(shí)例對象的非靜態(tài)synchronized方法,而線程B需要調(diào)用這個

實(shí)例對象所屬類的靜態(tài)synchronized方法,是允許的,不會發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài)

synchronized方法占用的鎖是當(dāng)前類的鎖,而訪問非靜態(tài)synchronized方法占用的鎖是當(dāng)前實(shí)

例對象鎖。

?修飾代碼塊:指定加鎖對象,對給定對象加鎖,進(jìn)入同步代碼庫前要獲得給定對象的鎖。

總結(jié):synchronized關(guān)鍵字加到static靜態(tài)方法和synchronized(class)代碼塊上都是是給Class

類上鎖。synchronized關(guān)鍵字加到實(shí)例方法上是給對象實(shí)例上鎖。盡量不要使用synchronized(String

a)因?yàn)镴VM中,字符串常量池具有緩存功能!

12.單例模式了解嗎?給我解釋一下雙重檢驗(yàn)鎖方式實(shí)現(xiàn)單例模式!

雙重校驗(yàn)鎖實(shí)現(xiàn)對象單例(線程安全)

說明:

?雙鎖機(jī)制的出現(xiàn)是為了解決前面同步問題和性能問題,看下面的代碼,簡單分析下確實(shí)是解決了多

線程并行進(jìn)來不會出現(xiàn)重復(fù)new對象,而且也實(shí)現(xiàn)了懶加載

publicclassSingleton{

privatevolatilestaticSingletonuniqueinstance;

privateSingleton(){}

publicstaticSingletongetuniquelnstance(){

//先判斷對象是否已經(jīng)實(shí)例過,沒有實(shí)例化過才進(jìn)入加鎖代碼

if(uniqueinstance==null){

//類對象加鎖

synchronized(Singleton.class){

if(uniqueinstance==null){

uniqueinstance=newSingleton。;

)

)

)

returnuniqueinstance;

)

另外,需要注意uniqueinstance采用volatile關(guān)鍵字修飾也是很有必要。

?uniqueinstance采用volatile關(guān)鍵字修飾也是很有必要的,uniqueinstance=newSingleton();

這段代碼其實(shí)是分為三步執(zhí)行:

1.為uniquelnstance分配內(nèi)存空間

2.初始化uniqueinstance

3.將uniqueinstance指向分配的內(nèi)存地址

但是由于具有指令重排的特性,執(zhí)行順序有可能變成指令重排在單線程環(huán)境下不會出現(xiàn)問題,

JVM1->3->20

但是在多線程環(huán)境下會導(dǎo)致一個線程獲得還沒有初始化的實(shí)例。例如,線程T1執(zhí)行J'1和3,此時丁2調(diào)用

getuniquelnstancef)后發(fā)現(xiàn)uniqueinstance不為空,因此返回uniqueinstance,但此時

uniqueinstance還未被初始化。

使用volatile可以禁止JVM的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。

13.說一下synchronized底層實(shí)現(xiàn)原理?

?Synchronized的語義底層是通過一個monitor(監(jiān)視器鎖)的對象來完成,

?每個對象有一個監(jiān)視器鎖(monitor)。每個Synchronized修飾過的代碼當(dāng)它的monitor被占用時就

會處于鎖定狀態(tài)并且嘗試獲取monitor的所有權(quán),過程:

1、如果monitor的進(jìn)入數(shù)為0,貝舷線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為

monitor的所有者。

2、如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1.

3、如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再

重新嘗試獲取monitor的所有權(quán)。

synchronized是可以通過反匯編指令javap命令,杏看相應(yīng)的字節(jié)碼文件。

14.synchronized可重入的原理

?重入鎖是指一個線程獲取到該鎖之后,該線程可以繼續(xù)獲得該鎖。底層原理維護(hù)一個計數(shù)器,當(dāng)線

程獲取該鎖時,計數(shù)器加一,再次獲得該鎖時繼續(xù)加一,釋放鎖時,計數(shù)器減一,當(dāng)計數(shù)器值為o

時,表明該鎖未被任何線程所持有,其它線程可以競爭獲取鎖。

15.什么是

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論