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

下載本文檔

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

文檔簡(jiǎn)介

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

目錄

基礎(chǔ)知識(shí)

多線程與高并發(fā)

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

1.響應(yīng)時(shí)間(ResponseTime)

2.吞吐量(Throughput)

3.每秒查詢率QPS(QueryPerSecond)

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

?多線程

多線程是java的特性,因?yàn)楝F(xiàn)在cpu都是多核多線程的,可以同時(shí)執(zhí)行幾個(gè)任務(wù),為了提高jvm的執(zhí)行效率,java提供了這種多線程的機(jī)

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

問請(qǐng)求。

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

Java多線程涉及技術(shù)點(diǎn)

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

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

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

可見性當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),如果其中一個(gè)線程對(duì)其作了修改,其他線程能立即獲取到最新的值。

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

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

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

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

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

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

3.悲觀鎖與樂觀鎖

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

樂觀鎖:每次操作不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止,不會(huì)造成線程阻塞。

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

5.synchronized關(guān)鍵字

6.CAS

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

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

7.線程池

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

很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線程和銷毀線程需要時(shí)間。線程池通過復(fù)用可以大大減少

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

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

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

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

3.應(yīng)用拆分:一個(gè)工程被拆分為多個(gè)工程部署,利用Dubb。解決多工程之間的通信。

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

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

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

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

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

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

?充分利用多核CPU的計(jì)算能力

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

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

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

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

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

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

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

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

?緩存導(dǎo)致的可見性問題

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

解決辦法:

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

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

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

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

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

并行:?jiǎn)挝粫r(shí)間內(nèi),多個(gè)處理器或多核處理器同時(shí)處理多個(gè)任務(wù),是真正意義上的"同時(shí)進(jìn)行”。

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

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

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

多線程的好處:

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

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

多線程的劣勢(shì):

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

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

線程之間對(duì)共享資源的訪問會(huì)相互影響,必須解決競(jìng)用共享資源的問題。

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

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

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

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

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

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

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

稱為輕權(quán)進(jìn)程或者輕量級(jí)進(jìn)程。

內(nèi)存分配:同一進(jìn)程的線程共享本進(jìn)程的地址空間和資源,而進(jìn)程之間的地址空間和資源是相互獨(dú)立的

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

執(zhí)行過程:每個(gè)獨(dú)立的進(jìn)程有程序運(yùn)行的入口、順序執(zhí)行序列和程序出口。但是線程不能獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提

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

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

進(jìn)程

一個(gè)在內(nèi)存中運(yùn)行的應(yīng)用程序。每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程可以有多個(gè)線程,比如在Windows系統(tǒng)中,一個(gè)運(yùn)行的

xx.exe就是一個(gè)進(jìn)程。

線程

進(jìn)程中的一個(gè)執(zhí)行任務(wù)(控制單元),負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行。一個(gè)進(jìn)程至少有一個(gè)線程,一個(gè)進(jìn)程可以運(yùn)行多個(gè)線程,多個(gè)線程可共

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

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

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

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

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

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

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

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

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

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

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

守護(hù)線程和用戶線程

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

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

都結(jié)束運(yùn)行,守護(hù)線程會(huì)隨JVM一起結(jié)束工作

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

比較明顯的區(qū)別之一是用戶線程結(jié)束,JVM退出,不管這個(gè)時(shí)候有沒有守護(hù)線程運(yùn)行。而守護(hù)線程不會(huì)影響JVM的退出。

注意事項(xiàng):

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

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

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

4.守護(hù)(Daemon)線程中不能依靠finally塊的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源的邏輯。因?yàn)槲覀兩厦嬉舱f過了一旦所有用戶線程都結(jié)

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

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

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

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

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

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

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

5.編輯/tmp/t.dat文件,查找線程號(hào)對(duì)應(yīng)的信息

什么是線程死鎖

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

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

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

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

形成死鎖的四個(gè)必要條件是什么

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

2.請(qǐng)求與保持條件:一個(gè)線程(進(jìn)程)因請(qǐng)求被占用資源而發(fā)生阻塞時(shí),對(duì)已獲得的資源保持不放。

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

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

如何避免線程死鎖?

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

破壞互斥條件

這個(gè)條件我們沒有辦法破壞,因?yàn)槲覀冇面i本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

破壞請(qǐng)求與保持條件

一次性申請(qǐng)所有的資源。

破壞不剝奪條件

占用部分資源的線程進(jìn)一步申請(qǐng)其他資源時(shí),如果申請(qǐng)不到,可以主動(dòng)釋放它占有的資源。

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

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

我們對(duì)線程2的代碼修改成下面這樣就不會(huì)產(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)視器鎖,這時(shí)候線程2就獲取不到了。然后線程1再去獲取resource2的監(jiān)視器鎖,可以獲取到。

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

避免了死鎖。

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

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

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

?繼承Thread類:

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

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

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

繼承Thread類

步驟

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

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

3.調(diào)用子類實(shí)例的star。方法來啟動(dòng)線程

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í)行結(jié)束");

)

運(yùn)行結(jié)果

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

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

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

步驟

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

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

3.調(diào)用線程對(duì)象的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í)行結(jié)果

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

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

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

步驟

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

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

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

4.調(diào)用線程對(duì)象的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("返回結(jié)果"+futureTask.get());

}catch(InterruptedExceptione){

e.printStackTrace();

}catch(ExecutionExceptione){

e.printStackTrace();

)

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

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

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

返回結(jié)果1

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

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

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

主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool

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

相同點(diǎn)

都是接口

都可以編寫多線程程序

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

主要區(qū)別

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

行的結(jié)果

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

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

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

每個(gè)線程都是通過某個(gè)特定Thread對(duì)象所對(duì)應(yīng)的方法run()來完成其操作的,run()方法稱為線程體。通過調(diào)用Thread類的start。方法來啟

動(dòng)一個(gè)線程。

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

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

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

CPU再調(diào)度其它線程。

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

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

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

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

new一個(gè)Thread,線程進(jìn)入了新建狀態(tài)。調(diào)用start()方法,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開始運(yùn)

行了。startO會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動(dòng)執(zhí)行run()方法的內(nèi)容,這是真正的多線程工作。

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

作。

總結(jié):調(diào)用start方法方可啟動(dòng)線程并使線程進(jìn)入就緒狀態(tài),而run方法只是thread的一個(gè)普通方法調(diào)用,還是在主線程里執(zhí)行。

什么是Callable和Future?

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

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

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

什么是FutureTask

FutureTask表示一個(gè)異步運(yùn)算的任務(wù)。FutureTask里面可以傳入一個(gè)Callable的具體實(shí)現(xiàn)類,可以對(duì)這個(gè)異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等

待獲取、判斷是否已經(jīng)完成、取消任務(wù)等操作。只有當(dāng)運(yùn)算完成的時(shí)候結(jié)果才能取回,如果運(yùn)算尚未完成get方法將會(huì)阻塞。一個(gè)

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

FutureTask也可以放入線程池中

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

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

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

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

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

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

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

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

阻塞的情況分三種:

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

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

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

(三).其他阻塞:通過調(diào)用線程的sleep?;騤oin。或發(fā)出了I/O請(qǐng)求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep。狀態(tài)超時(shí)、join。等待線程終止

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

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

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

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

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

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

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

分時(shí)調(diào)度模型是指讓所有的線程輪流獲得cpu的使用權(quán),并且平均分配每個(gè)線程占用的CPU的時(shí)間片這個(gè)也比較好理解。

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

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

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

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

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

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

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

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

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

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

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

程調(diào)度器的實(shí)現(xiàn)。

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

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

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

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

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

(3)notifyO:?jiǎn)拘岩粋€(gè)處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由JVM確定喚

醒哪個(gè)線程,而且與優(yōu)先級(jí)無關(guān);

(4)notifyAIIO:?jiǎn)拘阉刑幱诘却隣顟B(tài)的線程,該方法并不是將對(duì)象的鎖給所有線程,而是讓它們競(jìng)爭(zhēng),只有獲得鎖的線程才能進(jìn)入就

緒狀態(tài);

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

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

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

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

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

?用法不同:wait。方法被調(diào)用后,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對(duì)象上的notify?;蛘遪otifyAIIO方法。sleep。方

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

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

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

wait()方法應(yīng)該在循環(huán)調(diào)用,因?yàn)楫?dāng)線程獲取到CPU開始執(zhí)行的時(shí)候,其他條件可能還沒有滿足,所以在處理前,循環(huán)檢測(cè)條件是否滿足

會(huì)更好。下面是一段標(biāo)準(zhǔn)的使用wait和notify方法的代碼:

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

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

用的鎖,所以任意對(duì)象調(diào)用方法一定定義在Object類中。

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

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

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

這種設(shè)計(jì)并不是不能實(shí)現(xiàn),只是管理起來更加復(fù)雜。

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

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

當(dāng)一個(gè)線程需要調(diào)用對(duì)象的wait()方法的時(shí)候,這個(gè)線程必須擁有該對(duì)象的鎖,接著它就會(huì)釋放這個(gè)對(duì)象鎖并進(jìn)入等待狀態(tài)直到其他線程調(diào)

用這個(gè)對(duì)象上的notify。方法。同樣的,當(dāng)一個(gè)線程需要調(diào)用對(duì)象的notify。方法時(shí),它會(huì)釋放這個(gè)對(duì)象的鎖,以便其他在等待的線程就可

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

調(diào)用。

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

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

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

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

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

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

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

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

更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);

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

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

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

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

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

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

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

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

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

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

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

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

常。

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

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

islnterrupted:查看當(dāng)前中斷信號(hào)是true還是false

什么是阻塞式方法?

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

調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起,直到得到結(jié)果之后才會(huì)返回。此外,還有異步和非阻塞式方法在任務(wù)完成前就返回。

Java中你怎樣喚醒一個(gè)阻塞的線程?

首先,wait()snotifyO方法是針對(duì)對(duì)象的,調(diào)用任意對(duì)象的wait。方法都將導(dǎo)致線程阻塞,阻塞的同時(shí)也將釋放該對(duì)象的鎖,相應(yīng)地,調(diào)

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

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

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

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

如果線程調(diào)用了對(duì)象的wait。方法,那么線程便會(huì)處于該對(duì)象的等待池中,等待池中的線程不會(huì)去競(jìng)爭(zhēng)該對(duì)象的鎖。

notifyAIIO會(huì)喚醒所有的線程,notifyO只會(huì)喚醒一個(gè)線程。

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

次參與競(jìng)爭(zhēng)。而notify。只會(huì)喚醒一個(gè)線程,具體喚醒哪一個(gè)線程由虛擬機(jī)控制。

~I

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

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

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

安全性。

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

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

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

釋放對(duì)臨界資源(即隊(duì)列)的占用權(quán)。**因?yàn)樯a(chǎn)者如果不釋放對(duì)臨界資源的占用權(quán),那么消費(fèi)者就無法消費(fèi)隊(duì)列中的商品,就不會(huì)讓隊(duì)列

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

后等待消費(fèi)者消費(fèi)了商品,然后消費(fèi)者通知生產(chǎn)者隊(duì)列有空間了。同樣地,當(dāng)隊(duì)列空時(shí),消費(fèi)者也必須等待,等待生產(chǎn)者通知它隊(duì)列中有商

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

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

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

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

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

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

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

同步塊是更好的選擇,因?yàn)樗粫?huì)鎖住整個(gè)對(duì)象(當(dāng)然你也可以讓它鎖住整個(gè)對(duì)象)。同步方法會(huì)鎖住整個(gè)對(duì)象,哪怕這個(gè)類中有多個(gè)不相

關(guān)聯(lián)的同步塊,這通常會(huì)導(dǎo)致他們停止執(zhí)行并需要等待獲得這個(gè)對(duì)象上的鎖。

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

請(qǐng)知道一條原則:同步的范圍越小越好。

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

當(dāng)一個(gè)線程對(duì)共享的數(shù)據(jù)進(jìn)行操作時(shí),應(yīng)使之成為一個(gè)"原子操作即在沒有完成相關(guān)操作之前,不允許其他線程打斷它,否則,就會(huì)破

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

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

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

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

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

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

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

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

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

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

同步代碼塊:sychronized關(guān)鍵字修飾的代碼塊,被該關(guān)鍵字修飾的語句塊會(huì)自動(dòng)被加上內(nèi)置鎖,從而實(shí)現(xiàn)同步。

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

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

來修飾final類型的變量。

classAnimal{

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

privatevolatilestringcolor-"White";

publicstringgetColor()(

returncolor;

)

〃不需寨ynchronized

publicvoidrun(){

)

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

行為和語義。

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

classAnimal{

privatestringcolor:"White";

privateLocklock=newReetrantLock();

publicstringgetColor(){

returncolor;

)

publicvoideat(stringfood){

lock.lockf);

try{

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

}finally{

lock.unlock();

)

1

注:關(guān)于Lock對(duì)象和synchronized關(guān)鍵字的選擇:a.最好兩個(gè)都不用,使用一種java.util.concurrent包提供的機(jī)制,能夠幫助用戶處理

所有與鎖相關(guān)的代碼。b.如果synchronized關(guān)鍵字能滿足用戶的需求,就用synchronized,因?yàn)樗芎?jiǎn)化代碼c.如果需要更高級(jí)的功

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

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

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

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

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

能,每個(gè)對(duì)象都關(guān)聯(lián)著一把鎖。

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

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

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

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

這里區(qū)分一下:

(1)如果使用的是無界隊(duì)列LinkedBlockingQueue,也就是無界隊(duì)列的話,沒關(guān)系,繼續(xù)添加任務(wù)到阻塞隊(duì)列中等待執(zhí)行,因?yàn)?/p>

LinkedBlockingQueue可以近乎認(rèn)為是一個(gè)無窮大的隊(duì)列,可以無限存放任務(wù)

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

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

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

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

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

Servlet不是線程安全的,servlet是單實(shí)例多線程的,當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)方法,是不能保證共享變量的線程安全性的。

Struts2的action是多實(shí)例多線程的,是線程安全的,每個(gè)請(qǐng)求過來都會(huì)new一個(gè)新的action分配給這個(gè)請(qǐng)求,請(qǐng)求完成后銷毀。

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

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

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

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

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

方法二:使用自動(dòng)鎖synchronized。

方法三:使用手動(dòng)鎖Lock。

手動(dòng)鎖Java示例如下:

Locklock=newReentrantLock();

lock.Iock();

try(

System.out.printing獲得鎖)

}catch(Exceptione){

}finally{

System.out.printin("釋放鎖】

lock.unlockf);

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

每一個(gè)線程都是有優(yōu)先級(jí)的,一般來說,高優(yōu)先級(jí)的線程在運(yùn)行時(shí)會(huì)具有優(yōu)先權(quán),但這依賴于線程調(diào)度的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)是和操作系統(tǒng)相關(guān)

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

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

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

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

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

才是被線程自身所調(diào)用的。

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

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

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

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就會(huì)將線程的dump文件打印到標(biāo)準(zhǔn)輸出或錯(cuò)誤文件中,它可能打印在控

制臺(tái)或者日志文件中,具體位置依賴應(yīng)用的配置。

一個(gè)線程運(yùn)行時(shí)發(fā)生異常會(huì)怎樣?

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

接口。當(dāng)一個(gè)未捕獲異常將造成線程中斷的時(shí)候,JVM會(huì)使用Thread.getUncaughtExceptionHandler()來查詢線程的

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

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

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

消耗過多的CPU

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

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

降低穩(wěn)定性JVM

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

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

并發(fā)理論

Java內(nèi)存模型

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

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

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

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

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

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

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

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

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

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

的內(nèi)存,將首先調(diào)用該對(duì)象的finalize。方法,并且下一次垃圾回收動(dòng)作發(fā)生時(shí),才真正回收對(duì)象占用的內(nèi)存空間

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

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

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

為什么代碼會(huì)重排序?

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

足以下兩個(gè)條件:

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

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

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

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

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

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

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

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

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

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

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

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

個(gè)操作的執(zhí)行順序。例如:剛才的計(jì)算長(zhǎng)方形面積的程序,長(zhǎng)寬變量沒有任何關(guān)系,執(zhí)行順序改變也不會(huì)對(duì)最終結(jié)果造成任何的影響,所以

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

重排序帶來的問題

NassReorderExample{

inta=0;

booleanflag=false;

publicvoidwriter(){

a=1;

flagtrue;

publicvoidreader(){

if(flag){

inti=a'a;

)

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

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

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

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

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

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

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

synchronized

synchronized的作用?

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

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

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

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

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

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

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

來減少鎖操作的開銷。

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

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

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

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

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

而線程B需要調(diào)用這個(gè)實(shí)例對(duì)象所屬類的靜態(tài)synchronized方法,是允許的,不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài)synchronized方法

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

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

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

加到實(shí)例方法上是給對(duì)象實(shí)例上鎖。盡量不要使用synchronized(Stringa)因?yàn)镴VM中,字符串常量池具有緩存功能!

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

jblicclassSingleton{

privatevolatilestaticSingletonuniqueinstance;

privateSingleton(){

)

publicstaticSingletongetllniquelnstance(){

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

if(uniqueinstance==null){

/

synchronized(Singleton.class){

if(uniqueinstancenull){

uniqueinstancenewSingleton();

)

)

}

returnuniqueinstance;

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

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

溫馨提示

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

評(píng)論

0/150

提交評(píng)論