版權(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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度LNG運(yùn)輸車輛改裝與安全檢測(cè)合同3篇
- 2024年食品行業(yè)社會(huì)保險(xiǎn)管理與代繳合同
- 2025年度物流車輛智能系統(tǒng)升級(jí)合同4篇
- 2025年度醫(yī)療健康公司股權(quán)轉(zhuǎn)讓與產(chǎn)業(yè)鏈合作合同3篇
- 2025年度商業(yè)大廈車位包銷及物業(yè)管理合同4篇
- 2025年度智能倉儲(chǔ)物流系統(tǒng)建設(shè)承包經(jīng)營(yíng)協(xié)議4篇
- 2024石材行業(yè)石材應(yīng)用技術(shù)研究采購合同2篇
- 2025年度網(wǎng)絡(luò)直播個(gè)人勞務(wù)合同范本3篇
- 2025年度嬰幼兒專用牛奶采購合作協(xié)議書3篇
- 2025年電動(dòng)自行車品牌代理銷售合同標(biāo)準(zhǔn)版2篇
- 專利補(bǔ)正書實(shí)例
- 《動(dòng)物生理學(xué)》課程思政優(yōu)秀案例
- 高分子材料完整版課件
- DB37∕T 5118-2018 市政工程資料管理標(biāo)準(zhǔn)
- 大氣紅色商務(wù)展望未來贏戰(zhàn)集團(tuán)年會(huì)PPT模板課件
- T∕CAWA 002-2021 中國(guó)疼痛科專業(yè)團(tuán)體標(biāo)準(zhǔn)
- 住宅工程公共區(qū)域精裝修施工組織設(shè)計(jì)(217頁)
- 冷卻塔技術(shù)要求及質(zhì)量標(biāo)準(zhǔn)介紹
- (完整版)項(xiàng)目工程款收款收據(jù)
- 井點(diǎn)降水臺(tái)班記錄表
- 奇瑞汽車4S店各類表格模板
評(píng)論
0/150
提交評(píng)論