并發(fā)編程面試題_第1頁
并發(fā)編程面試題_第2頁
并發(fā)編程面試題_第3頁
并發(fā)編程面試題_第4頁
并發(fā)編程面試題_第5頁
已閱讀5頁,還剩28頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

并發(fā)編程面試題(持續(xù)更新.)

目錄

基礎知識

多線程與高并發(fā)

?高并發(fā)的處理指標包括

1.響應時間(ResponseTime)

2.吞吐量(Throughput)

3.每秒查詢率QPS(QueryPerSecond)

4.并發(fā)用戶數(shù)

?多線程

多線程是java的特性,因為現(xiàn)在cpu都是多核多線程的,可以同時執(zhí)行幾個任務,為了提高jvm的執(zhí)行效率,java提供了這種多線程的機

制,以增強數(shù)據(jù)處理效率。多線程對應的是cpu,高并發(fā)對應的是訪問請求,可以用單線程處理所有訪問請求,也可以用多線程同時處理訪

問請求。

總之,多線程即可以這么理解:多線程是處理高并發(fā)的一種編程方法,即并發(fā)需要用多線程實現(xiàn)。

Java多線程涉及技術點

1.并發(fā)編程三要素

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

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

可見性當多個線程訪問同一個變量時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。

2.線程的五大狀態(tài)

創(chuàng)建狀態(tài)當用new操作符創(chuàng)建一個線程的時候

就緒狀態(tài)調用start方法,處于就緒狀態(tài)的線程并不一定馬上就會執(zhí)行run方法,還需要等待CPU的調度

運行狀態(tài)CPU開始調度線程,并開始執(zhí)行run方法

阻塞狀態(tài)線程的執(zhí)行過程中由于一些原因進入阻塞狀態(tài)比如:調用sleep方法、嘗試去得到一個鎖等等

死亡狀態(tài)run方法執(zhí)行完或者執(zhí)行過程中遇到了一個異常。

3.悲觀鎖與樂觀鎖

悲觀鎖:每次操作都會加鎖,會造成線程阻塞。

樂觀鎖:每次操作不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止,不會造成線程阻塞。

4.線程之間的協(xié)作:wait/notify/notifyAII等

5.synchronized關鍵字

6.CAS

CAS全稱是CompareAndSwap,即比較替換,是實現(xiàn)并發(fā)應用到的一種技術。操作包含三個操作數(shù)一內存位置(V)、預期原值

(A)和新值(B)。如果內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。

7.線程池

如果我們使用線程的時候就去創(chuàng)建一個線程,雖然簡單,但是存在很大的問題。如果并發(fā)的線程數(shù)量很多,并且每個線程都是執(zhí)行一個時間

很短的任務就結束了,這樣頻繁創(chuàng)建線程就會大大降低系統(tǒng)的效率,因為頻繁創(chuàng)建線程和銷毀線程需要時間。線程池通過復用可以大大減少

線程頻繁創(chuàng)建與銷毀帶來的性能上的損耗。

高并發(fā)技術解決方案

1.分布式緩存:redis、memcachedW,結合CDN來解決圖片文件等訪問。

2.消息隊列中間件:RabbitMQ,RocketMQ,ActiveMQ等,解決大量消息的異步處理能力。

3.應用拆分:一個工程被拆分為多個工程部署,利用Dubb。解決多工程之間的通信。

4.數(shù)據(jù)庫垂直拆分和水平拆分(分庫分表)等。

5.數(shù)據(jù)庫讀寫分離,解決大數(shù)據(jù)的查詢問題。

6.還可以利用NoSql,例如MongoDB配合mysql組合使用。

7.還需要建立大數(shù)據(jù)訪問情況下的服務降級以及限流機制等。

并發(fā)編程的優(yōu)劣

為什么要使用并發(fā)編程(并發(fā)編程的優(yōu)點)

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

?方便進行業(yè)務拆分,提升系統(tǒng)并發(fā)能力和性能

并發(fā)編程有什么缺點

并發(fā)編程的目的就是為了能提高程序的執(zhí)行效率,提高程序運行速度,但是并發(fā)編程并不總是能提高程序運行速度的,而且并發(fā)編程可能會

遇到很多問題,比如:內存泄漏、上下文切換、線程安全、死鎖等問題。

并發(fā)編程三要素是什么?在Java程序中怎么保證多線程的運行安全?

三要素前文已經(jīng)提起過

出現(xiàn)線程安全問題的原因:

?線程切換帶來的原子性問題

?緩存導致的可見性問題

?編譯優(yōu)化帶來的有序性問題

解決辦法:

?JDKAtomic開頭的原子類、synchronized.LOCK,可以解決原子性問題

?^synchronizedvolatile、LOCK,**可以解決可見性問題

?Happens-Before規(guī)則可以解決有序性問題

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

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

并行:單位時間內,多個處理器或多核處理器同時處理多個任務,是真正意義上的"同時進行”。

串行:有n個任務,由一個線程按順序執(zhí)行。由于任務、方法都在一個線程執(zhí)行所以不存在線程不安全情況,也就不存在臨界區(qū)的問題。

什么是多線程,多線程的優(yōu)劣?

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

多線程的好處:

?可以提高CPU的利用率。在多線程程序中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程

序的效率。也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務。

多線程的劣勢:

線程也是程序,所以線程需要占用內存,線程越多占用內存也越多;

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

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

線程和進程區(qū)別

線程具有許多傳統(tǒng)進程所具有的特征,故又稱為輕型進程(Light-WeightProcess)或進程元;而把傳統(tǒng)的進程稱為重型進程(Heavy-

WeightProcess),它相當于只有一個線程的任務。在引入了線程的操作系統(tǒng)中,通常一個進程都有若干個線程,至少包含一個線程。

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

資源開銷:每個進程都有獨立的代碼和數(shù)據(jù)空間(程序上下文),程序之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線

程共享代碼和數(shù)據(jù)空間,每個線程都有自己獨立的運行棧和程序計數(shù)器(PC),線程之間切換的開銷小。

包含關系:如果一個進程內有多個線程,則執(zhí)行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被

稱為輕權進程或者輕量級進程。

內存分配:同一進程的線程共享本進程的地址空間和資源,而進程之間的地址空間和資源是相互獨立的

影響關系:一個進程崩潰后,在保護模式下不會對其他進程產(chǎn)生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。

執(zhí)行過程:每個獨立的進程有程序運行的入口、順序執(zhí)行序列和程序出口。但是線程不能獨立執(zhí)行,必須依存在應用程序中,由應用程序提

供多個線程執(zhí)行控制,兩者均可并發(fā)執(zhí)行

什么是線程和進程?

進程

一個在內存中運行的應用程序。每個進程都有自己獨立的一塊內存空間,一個進程可以有多個線程,比如在Windows系統(tǒng)中,一個運行的

xx.exe就是一個進程。

線程

進程中的一個執(zhí)行任務(控制單元),負責當前進程中程序的執(zhí)行。一個進程至少有一個線程,一個進程可以運行多個線程,多個線程可共

享數(shù)據(jù)。

什么是上下文切換?(重點)

多線程編程中一般線程的個數(shù)都大于CPU核心的個數(shù),而一個CPU核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效

執(zhí)行,CPU采取的策略是為每個線程分配時間片并輪轉的形式。當一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使

用.這個過程就屬于一次上下文切換。

概括來說就是:當前任務在執(zhí)行完CPU時間片切換到另一個任務之前會先保存自己的狀態(tài),以便下次再切換回這個任務時,可以再加載這

個任務的狀態(tài)。任務從保存到再加載的過程就是一次上下文切換。

上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時

間。所以,上下文切換對系統(tǒng)來說意味著消耗大量的CPU時間,事實上,可能是操作系統(tǒng)中時間消耗最大的操作。

Linux相比與其他操作系統(tǒng)(包括其他類Unix系統(tǒng))有很多的優(yōu)點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

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

守護線程和用戶線程

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

?守護(Daemon)線程:運行在后臺,為其他前臺線程服務。也可以說守護線程是JVM中非守護線程的“傭人”。一旦所有用戶線程

都結束運行,守護線程會隨JVM一起結束工作

main函數(shù)所在的線程就是一個用戶線程啊,main函數(shù)啟動的同時在JVM內部同時還啟動了好多守護線程,比如垃圾回收線程。

比較明顯的區(qū)別之一是用戶線程結束,JVM退出,不管這個時候有沒有守護線程運行。而守護線程不會影響JVM的退出。

注意事項:

1.setDaemon(true)必須在start))方法前執(zhí)行,否則會拋出UlegalThreadStateException異常

2.在守護線程中產(chǎn)生的新線程也是守護線程

3.不是所有的任務都可以分配給守護線程來執(zhí)行,比如讀寫操作或者計算邏輯

4.守護(Daemon)線程中不能依靠finally塊的內容來確保執(zhí)行關閉或清理資源的邏輯。因為我們上面也說過了一旦所有用戶線程都結

束運行,守護線程會隨JVM一起結束工作,所以守護(Daemon)線程中的finally語句塊可能無法被執(zhí)行。

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

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

1.找出cpu耗用厲害的進程pid,終端執(zhí)行top命令,然后按下shift+p查找出cpu利用最厲害的pid號

2.根據(jù)上面第一步拿到的pid號,top-H-ppido然后按下shift+p,查找出cpu利用率最厲害的線程號,比如top-H-p1328

3.將獲取到的線程號轉換成16進制,去百度轉換一下就行

4.使用jstack工具將進程信息打印輸出,jstackpid號〉/tmp/t.dat,比如jstack31365>/tmp/t.dat

5.編輯/tmp/t.dat文件,查找線程號對應的信息

什么是線程死鎖

百度百科:死鎖是指兩個或兩個以上的進程(線程)在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作

用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠在互相等待的進程(線程)稱為死鎖進程(線程)。

多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞,因此程序不可能正常終止。

如下圖所示,線程A持有資源2,線程B持有資源1.他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態(tài)。

形成死鎖的四個必要條件是什么

1.互斥條件:線程(進程)對于所分配到的資源具有排它性,即一個資源只能被一個線程(進程)占用,直到被該線程(進程)釋放

2.請求與保持條件:一個線程(進程)因請求被占用資源而發(fā)生阻塞時,對已獲得的資源保持不放。

3.不剝奪條件:線程(進程)已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源。

4.循環(huán)等待條件:當發(fā)生死鎖時,所等待的線程(進程)必定會形成一個環(huán)路(類似于死循環(huán)),造成永久阻塞

如何避免線程死鎖?

我們只要破壞產(chǎn)生死鎖的四個條件中的其中一個就可以了。

破壞互斥條件

這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

破壞請求與保持條件

一次性申請所有的資源。

破壞不剝奪條件

占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源。

破壞循環(huán)等待條件

靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環(huán)等待條件。

我們對線程2的代碼修改成下面這樣就不會產(chǎn)生死鎖了。

newThread(()->{

synchronized(resoureel){

System.out.printin(Thread.currentThread()+"getresource1");

try{

Thread.sleep(IOOO);

}catch(InterruptedExceptione){

e.printStackTrace();

)

System.out.printin(Thread.currentThread()+"waitinggetresource2");

synchronized(resource2){

System.out.printin(Thread.currentThread()+"getresource2");

)

)

,「線程2”).start();

輸出

Thread[線程1,5,main]getresoureel

Thread[線程1,5.main]waitinggetresource2

Thread修戔程1,5,main]getresource2

Thread[線程2.5,main]getresoureel

Thread[線程2.5,main]waitinggetresource2

Thread[線程2.5,main]getresource2

我們分析一下上面的代碼為什么避免了死鎖的發(fā)生?

線程1首先獲得到resource!的監(jiān)視器鎖,這時候線程2就獲取不到了。然后線程1再去獲取resource2的監(jiān)視器鎖,可以獲取到。

然后線程1釋放了對resource】、resource2的監(jiān)視器鎖的占用,線程2獲取到就可以執(zhí)行了。這樣就破壞了破壞循環(huán)等待條件,因此

避免了死鎖。

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

創(chuàng)建線程有哪幾種方式?

創(chuàng)建線程有四種方式:

?繼承Thread類:

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

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

?使用Executors工具類創(chuàng)建線程池

繼承Thread類

步驟

1.定義一個Thread類的子類,重寫run方法,將相關邏輯實現(xiàn),run()方法就是線程要執(zhí)行的業(yè)務邏輯方法

2.創(chuàng)建自定義的線程子類對象

3.調用子類實例的star。方法來啟動線程

jblicclassMyThreadextendsThread{

◎Override

publicvoidrun(){

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

)

jblicclassTheadTest{

publicstaticvoidmain(String[]args){

MyThreadmyThread-newMyThread();

myThread.start。;

System.out.printin(Thread.currentThread().getName()+"main。方法執(zhí)行結束");

)

運行結果

mainmain()方法執(zhí)行結束

Thread-run()方法正在執(zhí)行...

實現(xiàn)Runnable接口

步驟

1.定義Runnable接口實現(xiàn)類MyRunnable,并重寫run()方法

2.倉I」建MyRunnable實例myRunnable,以myRunnable作為target創(chuàng)建Thead對象,該Thread對象才是真正的線程對象

3.調用線程對象的start。方法

jblicclassMyRunnableimplementsRunnable{

(^Override

publicvoidrun(){

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

)

JblicclassRunnableTest{

publicstaticvoidmain(String。args){

MyRunnablemyRunnable-newMyRunnable();

ThreadthreadnewThread(myRunnable);

thread.start();

System.out.printin(Thread.currentThreadO.getName()+"main()方法執(zhí)行完成");

)

執(zhí)行結果

mainmain。方法執(zhí)行完成

Thread0run()方法執(zhí)行中...

實現(xiàn)Callable接口

步驟

1.創(chuàng)建實現(xiàn)Callable接口的類myCallable

2.以myCallable為參數(shù)創(chuàng)建FutureTask對象

3.將FutureTask作為參數(shù)創(chuàng)建Thread對象

4.調用線程對象的start。方法

iblicclassMyCallableimplementsCallable<lnteger>{

@Override

publicIntegercall(){

System.out.printin(Thread.currentThreadO.getName()r"call。方法執(zhí)行中

return1;

)

publicclassCallableTest{

publicstaticvoidmain(String[]args){

FutureTask<lnteger>futureTasknewFutureTask<lnteger>(newMyCallable。);

ThreadthreadnewThread(futureTask);

thread.start();

try(

Thread.sleep(IOOO);

System.out.printin("返回結果"+futureTask.get());

}catch(InterruptedExceptione){

e.printStackTrace();

}catch(ExecutionExceptione){

e.printStackTrace();

)

System.out.printin(Thread.currentThreadO.getName()+"main。方法執(zhí)行完成");

執(zhí)行結果

Thread-0call()方法執(zhí)行中…

返回結果1

Tainmain()方法執(zhí)行1完成

使用Executors工具類創(chuàng)建線程池

Executors提供了一系列工廠方法用于創(chuàng)先線程池,返回的線程池都實現(xiàn)了ExecutorService接口。

主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool

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

相同點

都是接口

都可以編寫多線程程序

都采用Thread.start()啟動線程

主要區(qū)別

?Runnable接口run方法無返回值;Callable接口call方法有返回值,是個泛型,和Future、FutureTask配合可以用來獲取異步執(zhí)

行的結果

?Runnable接口run方法只能拋出運行時異常,且無法捕獲處理;Callable接口call方法允許拋出異常,可以獲取異常信息

注:Callalbe接口支持返回執(zhí)行結果,需要調用FutureTask.get()得到,此方法會阻塞主進程的繼續(xù)往下執(zhí)行,如果不調用不會阻塞。

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

每個線程都是通過某個特定Thread對象所對應的方法run()來完成其操作的,run()方法稱為線程體。通過調用Thread類的start。方法來啟

動一個線程。

start。方法用于啟動線程,run()方法用于執(zhí)行線程的運行時代碼。run()可以重復調用,而start。只能調用一次。

start。方法來啟動一個線程,真正實現(xiàn)了多線程運行。調用start。方法無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行其他的代碼;

此時線程是處于就緒狀態(tài),并沒有運行。然后通過此Thread類調用方法run()來完成其運行狀態(tài),run()方法運行結束,此線程終止。然后

CPU再調度其它線程。

run()方法是在本線程里的,只是線程里的一個函數(shù),而不是多線程的。如果直接調用run(),其實就相當于是調用了一個普通函數(shù)而已,直

接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼,所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)

行時要使用start。方法而不是run()方法。

為什么我們調用start()方法時會執(zhí)行run()方法,為什么我們不能直接調用run()方法?(重要)

new一個Thread,線程進入了新建狀態(tài)。調用start()方法,會啟動一個線程并使線程進入了就緒狀態(tài),當分配到時間片后就可以開始運

行了。startO會執(zhí)行線程的相應準備工作,然后自動執(zhí)行run()方法的內容,這是真正的多線程工作。

而直接執(zhí)行run()方法,會把run方法當成一個main線程下的普通方法去執(zhí)行,并不會在某個線程中執(zhí)行它,所以這并不是多線程工

作。

總結:調用start方法方可啟動線程并使線程進入就緒狀態(tài),而run方法只是thread的一個普通方法調用,還是在主線程里執(zhí)行。

什么是Callable和Future?

Callable接口類似于Runnable,從名字就可以看出來了,但是Runnable不會返回結果,并且無法拋出返回結果的異常,而Callable功

能更強大一些,被線程執(zhí)行后,可以返回值,這個返回值可以被Future拿到,也就是說,F(xiàn)uture可以拿到異步執(zhí)行任務的返回值。

Future接口表示異步任務,是一個可能還沒有完成的異步任務的結果。所以說Callable用于產(chǎn)生結果,F(xiàn)uture用于獲取結果。

什么是FutureTask

FutureTask表示一個異步運算的任務。FutureTask里面可以傳入一個Callable的具體實現(xiàn)類,可以對這個異步運算的任務的結果進行等

待獲取、判斷是否已經(jīng)完成、取消任務等操作。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個

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

FutureTask也可以放入線程池中

線程的狀態(tài)和基本操作

說說線程的生命周期及五種基本狀態(tài)?

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

2.可運行(runnable):線程對象創(chuàng)建后,當調用線程對象的start。方法,該線程處于就緒狀態(tài),等待被線程調度選中,獲取的使用權。

3.運行(running):可運行狀態(tài)(runnable)的線程獲得了CPU時間片(timeslice),執(zhí)行程序代碼。注:就緒狀態(tài)是進入到運行狀態(tài)的

唯一入口,也就是說,線程要想進入運行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;

4.阻塞(block):處于運行狀態(tài)中的線程由于某種原因,暫時放棄對CPU的使用權,停止執(zhí)行,此時進入阻塞狀態(tài),直到其進入到就緒

狀態(tài),才有機會再次被CPU調用以進入到運行狀態(tài)。

阻塞的情況分三種:

(一).等待阻塞:運行狀態(tài)中的線程執(zhí)行wait。方法,JVM會把該線程放入等待隊列(waittingqueue)中,使本線程進入到等待阻塞狀態(tài);

(二).同步阻塞:線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),,則JVM會把該線程放入鎖池(lockpool)中,線程會

進入同步阻塞狀態(tài);

(三).其他阻塞:通過調用線程的sleep?;騤oin?;虬l(fā)出了I/O請求時,線程會進入到阻塞狀態(tài)。當sleep。狀態(tài)超時、join。等待線程終止

或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)。

5.死亡(dead):線程run()、main。方法執(zhí)行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次復生。

Java中用到的線程調度算法是什么?

計算機通常只有一個CPU,在任意時刻只能執(zhí)行一條機器指令,每個線程只有獲得CPU的使用權才能執(zhí)行指令。所謂多線程的并發(fā)運行,

其實是指從宏觀上看,各個線程輪流獲得CPU的使用權,分別執(zhí)行各自的任務。在運行池中,會有多個處于就緒狀態(tài)的線程在等待

CPU,JAVA虛擬機的一項任務就是負責線程的調度,線程調度是指按照特定機制為多個線程分配CPU的使用權。

有兩種調度模型:分時調度模型和搶占式調度模型。

分時調度模型是指讓所有的線程輪流獲得cpu的使用權,并且平均分配每個線程占用的CPU的時間片這個也比較好理解。

Java虛擬機采用搶占式調度模型,是指優(yōu)先讓可運行池中優(yōu)先級高的線程占用CPU,如果可運行池中的線程優(yōu)先級相同,那么就隨機選擇

一個線程,使其占用CPU。處于運行狀態(tài)的線程會一直運行,直至它不得不放棄CPUo

線程的調度策略

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

(1)線程體中調用了yield方法讓出了對CPU的占用權利

(2)線程體中調用了sleep方法使線程進入睡眠狀態(tài)

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

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

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

什么是線程調度器(ThreadScheduler)和時間分片(TimeSlicing)?

線程調度器是一個操作系統(tǒng)服務,它負責為Runnable狀態(tài)的線程分配CPU時間。一旦我們創(chuàng)建一個線程并啟動它,它的執(zhí)行便依賴于線

程調度器的實現(xiàn)。

時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基于線程優(yōu)先級或者線程等待的時間。

線程調度并不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴于線程的優(yōu)先級)。

請說出與線程同步以及線程調度相關的方法。

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

(2)sleepO:使一個正在運行的線程處于睡眠狀態(tài),是一個靜態(tài)方法,調用此方法要處理InterruptedException異常;

(3)notifyO:喚醒一個處于等待狀態(tài)的線程,當然在調用此方法的時候,并不能確切的喚醒某一個等待狀態(tài)的線程,而是由JVM確定喚

醒哪個線程,而且與優(yōu)先級無關;

(4)notifyAIIO:喚醒所有處于等待狀態(tài)的線程,該方法并不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進入就

緒狀態(tài);

sleep。和wait()有什么區(qū)別?(重點)

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

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

?是否釋放鎖:sleepO不釋放鎖;wait()釋放鎖。

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

?用法不同:wait。方法被調用后,線程不會自動蘇醒,需要別的線程調用同一個對象上的notify?;蛘遪otifyAIIO方法。sleep。方

法執(zhí)行完成后,線程會自動蘇醒?;蛘呖梢允褂脀ait(longtimeout)超時后線程會自動蘇醒。

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

處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒,如果不在循環(huán)中檢查等待條件,程序就會在沒有滿足結束條件的情況下退出。

wait()方法應該在循環(huán)調用,因為當線程獲取到CPU開始執(zhí)行的時候,其他條件可能還沒有滿足,所以在處理前,循環(huán)檢測條件是否滿足

會更好。下面是一段標準的使用wait和notify方法的代碼:

為什么線程通信的方法wait。,notify。和notifyAIIO被定義在Object類里?

Java中,任何對象都可以作為鎖,并且wait(),notify。等方法用于等待對象的鎖或者喚醒線程,在Java的線程中并沒有可供任何對象使

用的鎖,所以任意對象調用方法一定定義在Object類中。

wait(),notify。和notifyAIIO這些方法在同步代碼塊中調用

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

方法的實現(xiàn)。然而,這樣做有一個非常大的問題,一個線程完全可以持有很多鎖,你一個線程放棄鎖的時候,到底要放棄哪個鎖?當然了,

這種設計并不是不能實現(xiàn),只是管理起來更加復雜。

綜上所述,wait。、notify。和notifyAIIO方法要定義在Object類中。

為什么walto,notifyO和notifyAIIO必須在同步方法或者同步塊中被調用?

當一個線程需要調用對象的wait()方法的時候,這個線程必須擁有該對象的鎖,接著它就會釋放這個對象鎖并進入等待狀態(tài)直到其他線程調

用這個對象上的notify。方法。同樣的,當一個線程需要調用對象的notify。方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可

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

調用。

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

使當前線程從運行狀態(tài)變?yōu)榫途w狀態(tài)。

當前線程到了就緒狀態(tài),那么接下來哪個線程會從就緒狀態(tài)變成執(zhí)行狀態(tài)呢?可能是當前線程,也可能是其他線程,看系統(tǒng)的分配了。

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

Thread類的sleep。和yield。方法招在當前正在執(zhí)行的線程上運行。所以在其他處于等待狀態(tài)的線程上調用這些方法是沒有意義的,這就是

為什么這些方法是靜態(tài)的。它們可以在當前正在執(zhí)行的線程中工作,并避免程序員錯誤的認為可以在其他非運行線程調用這些方法。

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

(1)sleep。方法給其他線程運行機會時不考慮線程的優(yōu)先級,因此會給低優(yōu)先級的線程以運行的機會;yield。方法只會給相同優(yōu)先級或

更高優(yōu)先級的線程以運行的機會;

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

⑶sleep。方法聲明拋出InterruptedException,而yield。方法沒有聲明任何異常;

(4)sleep。方法比yield。方法(跟操作系統(tǒng)CPU調度相關)具有更好的可移植性,通常不建議使用yield。方法來控制并發(fā)線程的執(zhí)行。

如何停止一個正在運行的線程?

在java中有以下3種方法可以終止正在運行的線程:

1.使用退出標志,使線程正常退出,也就是當run方法完成后線程終止。

2.使用stop方法強行終止,但是不推薦這個方法,因為stop和suspend及resume一樣都是過期作廢的方法。

3.使用interrupt方法中斷線程。

Java中Interrupted和Islnterrupted方法的區(qū)別?

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

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

程中斷后會拋出interruptedException的方法)就是在監(jiān)視線程的中斷狀態(tài),一旦線程的中斷狀態(tài)被置為“中斷狀態(tài)”,就會拋出中斷異

常。

interrupted:是靜態(tài)方法,查看當前中斷信號是true還是false并且清除中斷信號。如果一個線程被中斷了,第一次調用interrupted則返

回true,第二次和后面的就返回false了。

islnterrupted:查看當前中斷信號是true還是false

什么是阻塞式方法?

阻塞式方法是指程序會一直等待該方法完成期間不做其他事情,ServerSocket的accept。方法就是一直等待客戶端連接。這里的阻塞是指

調用結果返回之前,當前線程會被掛起,直到得到結果之后才會返回。此外,還有異步和非阻塞式方法在任務完成前就返回。

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

首先,wait()snotifyO方法是針對對象的,調用任意對象的wait。方法都將導致線程阻塞,阻塞的同時也將釋放該對象的鎖,相應地,調

用任意對象的notify。方法則將隨機解除該對象阻塞的線程,但它需要重新獲取該對象的鎖,直到獲取成功才能往下執(zhí)行;

其次,wait、notify方法必須在synchronized塊或方法中被調用,并且要保證同步塊或方法的鎖對象與調用wait、notify方法的對象是

同一個,如此一來在調用wait之前當前線程就已經(jīng)成功獲取某對象的鎖,執(zhí)行wait阻塞后當前線程就將之前獲取的對象鎖釋放。

notifyO和notifyAIIQ有什么區(qū)別?(重點)

如果線程調用了對象的wait。方法,那么線程便會處于該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

notifyAIIO會喚醒所有的線程,notifyO只會喚醒一個線程。

notifyAIIO調用后,會將全部線程由等待池移到鎖池,然后參與鎖的競爭,競爭成功則繼續(xù)執(zhí)行,如果不成功則留在鎖池等待鎖被釋放后再

次參與競爭。而notify。只會喚醒一個線程,具體喚醒哪一個線程由虛擬機控制。

~I

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

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

一般來說,共享變量要求變量本身是線程安全的,然后在線程內使用的時候,如果有對共享變量的復合操作,那么也得保證復合操作的線程

安全性。

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

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

比如說最經(jīng)典的生產(chǎn)者-消費者模型:當隊列滿時,生產(chǎn)者需要等待隊列有空間才能繼續(xù)往里面放入商品,而**在等待的期間內,生產(chǎn)者必須

釋放對臨界資源(即隊列)的占用權。**因為生產(chǎn)者如果不釋放對臨界資源的占用權,那么消費者就無法消費隊列中的商品,就不會讓隊列

有空間,那么生產(chǎn)者就會一直無限等待下去。因此,一般情況下,當隊列滿時,會讓生產(chǎn)者交出對臨界資源的占用權,并進入掛起狀態(tài)。然

后等待消費者消費了商品,然后消費者通知生產(chǎn)者隊列有空間了。同樣地,當隊列空時,消費者也必須等待,等待生產(chǎn)者通知它隊列中有商

品了。這種互相通信的過程就是線程間的協(xié)作。

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

一.syncrhoized力口鎖的線程的Object類的wait()/notify()/notifyAII()

二.ReentrantLock類加鎖的線程的Condition類的await()/signal()/signalAII()

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

三.通過管道進行線程間通信:1)字節(jié)流;2)字符流

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

同步塊是更好的選擇,因為它不會鎖住整個對象(當然你也可以讓它鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相

關聯(lián)的同步塊,這通常會導致他們停止執(zhí)行并需要等待獲得這個對象上的鎖。

同步塊更要符合開放調用的原則,只在需要鎖住的代碼塊鎖住相應的對象,這樣從側面來說也可以避免死鎖。

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

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

當一個線程對共享的數(shù)據(jù)進行操作時,應使之成為一個"原子操作即在沒有完成相關操作之前,不允許其他線程打斷它,否則,就會破

壞數(shù)據(jù)的完整性,必然會得到錯誤的處理結果,這就是線程的同步。

在多線程應用中,考慮不同線程之間的數(shù)據(jù)同步和防止死鎖。當兩個或多個線程之間同時等待對方釋放資源的時候就會形成線程之間的死

鎖。為了防止死鎖的發(fā)生,需要通過同步來實現(xiàn)線程安全。

線程互斥是指對于共享的進程系統(tǒng)資源,在各單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多只允許一

個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。

線程間的同步方法大體可分為兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統(tǒng)內核對象的單一性來進行同步,使用時需要

切換內核態(tài)與用戶態(tài),而用戶模式就是不需要切換到內核態(tài),只在用戶態(tài)完成操作。

用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區(qū)。內核模式下的方法有:事件,信號量,互斥量。

實現(xiàn)線程同步的五種方法

同步代碼方法:sychronized關鍵字修飾的方法

同步代碼塊:sychronized關鍵字修飾的代碼塊,被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現(xiàn)同步。

使用特殊域變量volatile實現(xiàn)線程同步:volatile關鍵字為域變量的訪問提供了一種免鎖機制;使用volatile修飾域相當于告訴虛擬機該

域可能會被其他線程更新;因此每次使用該域就要重新計算,而不是使用寄存器中的值;volatile不會提供任何原子操作,它也不能用

來修飾final類型的變量。

classAnimal{

腐要同步的變量上加ko/a加e

privatevolatilestringcolor-"White";

publicstringgetColor()(

returncolor;

)

〃不需寨ynchronized

publicvoidrun(){

)

?使用重入鎖實現(xiàn)線程同步:Reentrantlock類是可重入、互斥、實現(xiàn)了Lock接口的鎖,它與sychronized方法和方法塊具有相同的基本

行為和語義。

ReentrantLock常用的方法有:ReetrantLock():創(chuàng)建一個ReetrantLock實例;Lock。:獲得鎖;unlock。:釋放鎖

classAnimal{

privatestringcolor:"White";

privateLocklock=newReetrantLock();

publicstringgetColor(){

returncolor;

)

publicvoideat(stringfood){

lock.lockf);

try{

System.out.println(eat+"food");

}finally{

lock.unlock();

)

1

注:關于Lock對象和synchronized關鍵字的選擇:a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助用戶處理

所有與鎖相關的代碼。b.如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼c.如果需要更高級的功

能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現(xiàn)死鎖,通常在finally代碼釋放鎖。

?使用局部變量實現(xiàn)線程同步:如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨

立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。

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

在java虛擬機中,每個對象(Object和class)通過某種邏輯關聯(lián)監(jiān)視器,每個監(jiān)視器和一個對象引用相關聯(lián),為了實現(xiàn)監(jiān)視器的互斥功

能,每個對象都關聯(lián)著一把鎖。

一旦方法或者代碼塊被synchronized修飾,那么這個部分就放入了監(jiān)視器的監(jiān)視區(qū)域,確保一次只能有一個線程執(zhí)行該部分的代碼,線程

在獲取鎖之前不允許執(zhí)行該部分的代碼

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

如果你提交任務時,線程池隊列已滿,這時會發(fā)生什么?

這里區(qū)分一下:

(1)如果使用的是無界隊列LinkedBlockingQueue,也就是無界隊列的話,沒關系,繼續(xù)添加任務到阻塞隊列中等待執(zhí)行,因為

LinkedBlockingQueue可以近乎認為是一個無窮大的隊列,可以無限存放任務

(2)如果使用的是有界隊列比如ArrayBlockingQueue,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,

會根據(jù)maximumPoolSize的值增加線程數(shù)量,如果增加了線程數(shù)量還是處理不過來,ArrayBlockingQueue繼續(xù)滿,那么則會使用拒絕

策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy

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

線程安全是編程中的術語,指某個方法在多線程環(huán)境中被調用時,能夠正確地處理多個線程之間的共享變量,使程序功能正確完成。

Servlet不是線程安全的,servlet是單實例多線程的,當多個線程同時訪問同一個方法,是不能保證共享變量的線程安全性的。

Struts2的action是多實例多線程的,是線程安全的,每個請求過來都會new一個新的action分配給這個請求,請求完成后銷毀。

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

Struts2好處是不用考慮線程安全問題;Servlet和SpringMVC需要考慮線程安全問題,但是性能可以提升不用處理太多的gc,可以使

用ThreadLocal來處理多線程的問題。

在Java程序中怎么保證多線程的運行安全?

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

方法二:使用自動鎖synchronized。

方法三:使用手動鎖Lock。

手動鎖Java示例如下:

Locklock=newReentrantLock();

lock.Iock();

try(

System.out.printing獲得鎖)

}catch(Exceptione){

}finally{

System.out.printin("釋放鎖】

lock.unlockf);

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

每一個線程都是有優(yōu)先級的,一般來說,高優(yōu)先級的線程在運行時會具有優(yōu)先權,但這依賴于線程調度的實現(xiàn),這個實現(xiàn)是和操作系統(tǒng)相關

的(OSdependent)。我們可以定義線程的優(yōu)先級,但是這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行。線程優(yōu)先級是一個int

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

Java的線程優(yōu)先級調度會委托給操作系統(tǒng)去處理,所以與具體的操作系統(tǒng)優(yōu)先級有關,如非特別需要,一般無需設置線程優(yōu)先級。

線程類的構造方法、靜態(tài)塊是被哪個線程調用的

這是一個非常刁鉆和狡猾的問題。請記?。壕€程類的構造方法、靜態(tài)塊是被new這個線程類所在的線程所調用的,而run方法里面的代碼

才是被線程自身所調用的。

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

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

(2)Threadl的構造方法、靜態(tài)塊是Thread2調用的,Threadl的run()方法是Threadl自己調用的

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

Dump文件是進程的內存鏡像。可以把程序的執(zhí)行狀態(tài)通過調試器保存到dump文件中。

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

在Windows下,你可以按下Ctrl+Break來獲取。這樣JVM就會將線程的dump文件打印到標準輸出或錯誤文件中,它可能打印在控

制臺或者日志文件中,具體位置依賴應用的配置。

一個線程運行時發(fā)生異常會怎樣?

如果異常沒有被捕獲該線程將會停止執(zhí)行。Thread.UncaughtExceptionHandler是用于處理未捕獲異常造成線程突然中斷情況的一個內嵌

接口。當一個未捕獲異常將造成線程中斷的時候,JVM會使用Thread.getUncaughtExceptionHandler()來查詢線程的

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

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

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

消耗過多的CPU

資源如果可運行的線程數(shù)量多于可用處理器的數(shù)量,那么有線程將會被閑置。大量空閑的線程會占用許多內存,給垃圾回收器帶來壓

力,而且大量的線程在競爭CPU資源時還將產(chǎn)生其他性能的開銷。

降低穩(wěn)定性JVM

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

構造函數(shù)中請求棧的大小,以及底層操作系統(tǒng)對線程的限制等。如果破壞了這些限制,那么可能拋出OutOfMemoryError異常。

并發(fā)理論

Java內存模型

Java中垃圾回收有什么目的?什么時候進行垃圾回收?

垃圾回收是在內存中存在沒有引用的對象或超過作用域的對象時進行的。

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

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

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

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

finalize。方法什么時候被調用?析構函數(shù)(finalization的目的是什么?

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

finalize是Object類的一個方法,該方法在Object類中的聲明protectedvoidfinalizeOthrowsThrowable{}

在垃圾回收器執(zhí)行時會調用被回收對象的finalize。方法,可以覆蓋此方法來實現(xiàn)對其資源的回收。注意:一旦垃圾回收器準備釋放對象占用

的內存,將首先調用該對象的finalize。方法,并且下一次垃圾回收動作發(fā)生時,才真正回收對象占用的內存空間

2)GC本來就是內存回收了,應用還需要在finalization做什么呢?答案是大部分時候,什么都不用做(也就是不需要重載)。只有在某些很

特殊的情況下,比如你調用了一些native的方法(一般是C寫的),可以要在finaliztion里去調用C的釋放函數(shù)。

重排序與數(shù)據(jù)依賴性

為什么代碼會重排序?

在執(zhí)行程序時,為了提高性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎么排序就怎么排序,它需要滿

足以下兩個條件:

在單線程環(huán)境下不能改變程序運行的結果;

存在數(shù)據(jù)依賴關系的不允許重排序

需要注意的是:重排序不會影響單線程環(huán)境的執(zhí)行結果,但是會破壞多線程的執(zhí)行語義。

as-if-seria覷則和happens-befor砌!則的區(qū)別?(JMM方面內容)

as-if-serial語義保證單線程內程序的執(zhí)行結果不被改變,happens-before關系保證正確同步的多線程程序的執(zhí)行結果不被改變。

as-if-serial語義給編寫單線程程序的程序員創(chuàng)造了一個幻境:單線程程序是按程序的順序來執(zhí)行的。happens-before關系給編寫正確

同步的多線程程序的程序員創(chuàng)造了一個幻境:正確同步的多線程程序是按happens-before指定的順序來執(zhí)行的。

as-if-serial語義和happens-before這么做的目的,都是為了在不改變程序執(zhí)行結果的前提下,盡可能地提高程序執(zhí)行的并行度。

數(shù)據(jù)依賴

定義:如果兩個操作訪問同一個變量,且這兩個操作有至少有一個為寫操作,此時這兩個操作就存在數(shù)據(jù)依賴性

三種情況:讀后寫、寫后寫、寫后讀。只要重排序兩個操作的執(zhí)行順序,那么程序的執(zhí)行結果將會被改變。

如果重排序會對最終執(zhí)行結果產(chǎn)生影響,編譯器和處理器在重排時,會遵守數(shù)據(jù)依賴性,編譯器和處理器不會改變存在數(shù)據(jù)依賴性關系的兩

個操作的執(zhí)行順序。例如:剛才的計算長方形面積的程序,長寬變量沒有任何關系,執(zhí)行順序改變也不會對最終結果造成任何的影響,所以

可以說長寬沒有數(shù)據(jù)依賴性。

重排序帶來的問題

NassReorderExample{

inta=0;

booleanflag=false;

publicvoidwriter(){

a=1;

flagtrue;

publicvoidreader(){

if(flag){

inti=a'a;

)

我們開兩個線程AB,分別執(zhí)行writer和reader,flag為標志位,用來判斷a是否被寫入,則我們的線程B執(zhí)行4操作時,能否看到線程A對a

的寫操作?不一定,12操作并沒有數(shù)據(jù)依賴性,編譯器和處理器可以對這兩個操作進行重排序,也就是說可能A執(zhí)行2后,B直接執(zhí)行3,判

斷為true*接著執(zhí)行4,而此時a還沒有被寫入。這樣多線程程序的語義就被重排序破壞了。

編譯器和處理器可能會對操作重排序,這個是要遵守數(shù)據(jù)依賴性的,即不會改變存在數(shù)據(jù)依賴關系的兩個操作的執(zhí)行順序。這里所說的數(shù)據(jù)

依賴性僅僅針對單個處理器中執(zhí)行的指令序列和單個線程中執(zhí)行的操作,不同處理器之間和不同線程之間的數(shù)據(jù)依賴性不被編譯器和處理器

考慮。所以在并發(fā)編程下這就有一些問題了。

并發(fā)關鍵字(重要)

synchronized

synchronized的作用?

在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環(huán)境下,控制synchronized代碼段不被多個線程同時執(zhí)行。

synchronized可以修飾類、方法、變量。

另外,在Java早期版本中,synchronized屬于重量級鎖,效率低下,因為監(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的Mutex

Lock來實現(xiàn)的,Java的線程是映射到操作系統(tǒng)的原生線程之上的。如果要掛起或者喚醒一個線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)

實現(xiàn)線程之間的切換時需要從用戶態(tài)轉換到內核態(tài),這個狀態(tài)之間的轉換需要相對比較長的時間,時間成本相對較高,這也是為什么早期的

synchronized效率低的原因。慶幸的是在Java6之后Java官方對從JVM層面對synchronized較大優(yōu)化,所以現(xiàn)在的synchronized

鎖效率也優(yōu)化得很不錯了。JDK1.6對鎖的實現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術

來減少鎖操作的開銷。

說說自己是怎么使用synchronized關鍵字,在項目中用到了嗎?

synchronized關鍵字最主要的三種使用方式:

?修飾實例方法:作用于當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖

?修飾靜態(tài)方法:也就是給當前類加鎖,會作用于類的所有對象實例,因為靜態(tài)成員不屬于任何一個實例對象,是類成員(static表明這

是該類的一個靜態(tài)資源,不管new了多少個對象,只有一份)。所以如果一個線程A調用一個實例對象的非靜態(tài)synchronized方法,

而線程B需要調用這個實例對象所屬類的靜態(tài)synchronized方法,是允許的,不會發(fā)生互斥現(xiàn)象,因為訪問靜態(tài)synchronized方法

占用的鎖是當前類的鎖,而訪問非靜態(tài)synchronized方法占用的鎖是當前實例對象鎖。

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

總結:synchronized關鍵字加到static靜態(tài)方法和synchronized(class)代碼塊上都是是給Class類上鎖。synchronized關鍵字

加到實例方法上是給對象實例上鎖。盡量不要使用synchronized(Stringa)因為JVM中,字符串常量池具有緩存功能!

面試中面試官經(jīng)常會說:“單例模式了解嗎?來給我手寫一下!給我解釋一下雙重檢驗鎖方式實現(xiàn)單例模式的原理唄!”

jblicclassSingleton{

privatevolatilestaticSingletonuniqueinstance;

privateSingleton(){

)

publicstaticSingletongetllniquelnstance(){

力先判斷對象是否已經(jīng)實例過,沒有實例化過

if(uniqueinstance==null){

/

synchronized(Singleton.class){

if(uniqueinstancenull){

uniqueinstancenewSingleton();

)

)

}

returnuniqueinstance;

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

uniqueinstance采用volatile關鍵字修飾也是很有必要的,uniqueinstance=

溫馨提示

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

評論

0/150

提交評論