五、多線程
35. 并行和并發(fā)有什么區(qū)別唯咬?
并發(fā)(concurrency)和并行(parallellism)是:
- 并行是指兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生晌柬;而并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔發(fā)生。
- 并行是在不同實(shí)體上的多個(gè)事件凡伊,并發(fā)是在同一實(shí)體上的多個(gè)事件唧席。比如在單核CPU系統(tǒng)上擦盾,只可能存在并發(fā)而不可能存在并行嘲驾。
- 并行是在一臺(tái)處理器上“同時(shí)”處理多個(gè)任務(wù),在多臺(tái)處理器上同時(shí)處理多個(gè)任務(wù)厌衙。如hadoop分布式集群
所以并發(fā)編程的目標(biāo)是充分的利用處理器的每一個(gè)核距淫,以達(dá)到最高的處理性能绞绒。那為什么并發(fā)就能充分利用cpu的執(zhí)行能力
首先執(zhí)行多個(gè)任務(wù)如果是串行執(zhí)行那么cpu一定會(huì)存在等待一個(gè)任務(wù)執(zhí)行完再去執(zhí)行下一個(gè)任務(wù)婶希,但是如果是并發(fā)開(kāi)啟多個(gè)線程去分別執(zhí)行不同的任務(wù)的時(shí)候,這個(gè)時(shí)候便可以充分的利用cpu蓬衡,多個(gè)線程進(jìn)行切換去搶占cpu喻杈,cpu的空閑時(shí)間就會(huì)減少。
36. 線程和進(jìn)程的區(qū)別狰晚?
進(jìn)程:是執(zhí)行中一段程序筒饰,即一旦程序被載入到內(nèi)存中并準(zhǔn)備執(zhí)行,它就是一個(gè)進(jìn)程壁晒。進(jìn)程是表示資源分配的的基本概念瓷们,又是調(diào)度運(yùn)行的基本單位,是系統(tǒng)中的并發(fā)執(zhí)行的單位秒咐。
線程:?jiǎn)蝹€(gè)進(jìn)程中執(zhí)行中每個(gè)任務(wù)就是一個(gè)線程谬晕。線程是進(jìn)程中執(zhí)行運(yùn)算的最小單位。
一個(gè)線程只能屬于一個(gè)進(jìn)程携取,但是一個(gè)進(jìn)程可以擁有多個(gè)線程攒钳。多線程處理就是允許一個(gè)進(jìn)程中在同一時(shí)刻執(zhí)行多個(gè)任務(wù)。
進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式雷滋。進(jìn)程有獨(dú)立的地址空間不撑,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響晤斩,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑焕檬。線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間澳泵,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉揩页,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí)烹俗,耗費(fèi)資源較大爆侣,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作幢妄,只能用線程兔仰,不能用進(jìn)程。
- 簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.**
- 線程的劃分尺度小于進(jìn)程蕉鸳,使得多線程程序的并發(fā)性高乎赴。
- 另外忍法,進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存榕吼,從而極大地提高了程序的運(yùn)行效率饿序。
- 線程在執(zhí)行過(guò)程中與進(jìn)程還是有區(qū)別的。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口羹蚣、順序執(zhí)行序列和程序的出口原探。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中顽素,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制咽弦。
- 從邏輯角度來(lái)看,多線程的意義在于一個(gè)應(yīng)用程序中胁出,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行型型。但操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配全蝶。這就是進(jìn)程和線程的重要區(qū)別闹蒜。
- 線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):
線程執(zhí)行開(kāi)銷小,但不利于資源的管理和保護(hù)抑淫;
而進(jìn)程正相反绷落。
同時(shí),線程適合于在SMP機(jī)器上運(yùn)行丈冬,而進(jìn)程則可以跨機(jī)器遷移嘱函。
- 線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):
37. 守護(hù)線程是什么?
守護(hù)線程(即daemon thread)埂蕊,是個(gè)服務(wù)線程往弓,準(zhǔn)確地來(lái)說(shuō)就是服務(wù)其他的線程,這是它的作用——而其他的線程只有一種蓄氧,那就是用戶線程函似。所以java里線程分2種,
1喉童、守護(hù)線程撇寞,比如垃圾回收線程,就是最典型的守護(hù)線程堂氯。
2蔑担、用戶線程,就是應(yīng)用程序里的自定義線程咽白。
將線程轉(zhuǎn)換為守護(hù)線程可以通過(guò)調(diào)用Thread對(duì)象的setDaemon(true)方法來(lái)實(shí)現(xiàn)啤握。在使用守護(hù)線程時(shí)需要注意一下幾點(diǎn):
(1) thread.setDaemon(true)必須在thread.start()之前設(shè)置,否則會(huì)跑出一個(gè)IllegalThreadStateException異常晶框。你不能把正在運(yùn)行的常規(guī)線程設(shè)置為守護(hù)線程排抬。
(2) 在Daemon線程中產(chǎn)生的新線程也是Daemon的懂从。
(3) 守護(hù)線程應(yīng)該永遠(yuǎn)不去訪問(wèn)固有資源,如文件蹲蒲、數(shù)據(jù)庫(kù)番甩,因?yàn)樗鼤?huì)在任何時(shí)候甚至在一個(gè)操作的中間發(fā)生中斷
守護(hù)線程和用戶線程的沒(méi)啥本質(zhì)的區(qū)別:唯一的不同之處就在于虛擬機(jī)的離開(kāi):如果用戶線程已經(jīng)全部退出運(yùn)行了,只剩下守護(hù)線程存在了届搁,虛擬機(jī)也就退出了缘薛。 因?yàn)闆](méi)有了被守護(hù)者,守護(hù)線程也就沒(méi)有工作可做了咖祭,也就沒(méi)有繼續(xù)運(yùn)行程序的必要了掩宜。
38. 創(chuàng)建線程有哪幾種方式蔫骂?
一么翰、繼承Thread類創(chuàng)建線程類
二、通過(guò)Runnable接口創(chuàng)建線程類
三辽旋、通過(guò)Callable和Future創(chuàng)建線程
采用實(shí)現(xiàn)Runnable浩嫌、Callable接口的方式創(chuàng)見(jiàn)多線程時(shí),優(yōu)勢(shì)是:
線程類只是實(shí)現(xiàn)了Runnable接口或Callable接口补胚,還可以繼承其他類码耐。
在這種方式下,多個(gè)線程可以共享同一個(gè)target對(duì)象溶其,所以非常適合多個(gè)相同線程來(lái)處理同一份資源的情況骚腥,從而可以將CPU、代碼和數(shù)據(jù)分開(kāi)瓶逃,形成清晰的模型束铭,較好地體現(xiàn)了面向?qū)ο蟮乃枷搿?/p>
劣勢(shì)是:
編程稍微復(fù)雜,如果要訪問(wèn)當(dāng)前線程厢绝,則必須使用Thread.currentThread()方法契沫。
使用繼承Thread類的方式創(chuàng)建多線程時(shí)優(yōu)勢(shì)是:
編寫(xiě)簡(jiǎn)單,如果需要訪問(wèn)當(dāng)前線程昔汉,則無(wú)需使用Thread.currentThread()方法懈万,直接使用this即可獲得當(dāng)前線程。
劣勢(shì)是:
線程類已經(jīng)繼承了Thread類靶病,所以不能再繼承其他父類会通。
39. 說(shuō)一下 runnable 和 callable 有什么區(qū)別?
相同點(diǎn)
- 都是接口
- 都可以編寫(xiě)多線程程序
- 都采用Thread.start()啟動(dòng)線程
不同點(diǎn)
- Runnable沒(méi)有返回值娄周;Callable可以返回執(zhí)行結(jié)果涕侈,是個(gè)泛型,和Future昆咽、FutureTask配合可以用來(lái)獲取異步執(zhí)行的結(jié)果
- Callable接口的call()方法允許拋出異常驾凶;Runnable的run()方法異常只能在內(nèi)部消化牙甫,不能往上繼續(xù)拋
注:Callalbe接口支持返回執(zhí)行結(jié)果,需要調(diào)用FutureTask.get()得到调违,此方法會(huì)阻塞主進(jìn)程的繼續(xù)往下執(zhí)行窟哺,如果不調(diào)用不會(huì)阻塞。
40. 線程有哪些狀態(tài)技肩?
Java中的線程的生命周期大體可分為5種狀態(tài)且轨。
- 新建(NEW):新創(chuàng)建了一個(gè)線程對(duì)象。
- 可運(yùn)行(RUNNABLE):線程對(duì)象創(chuàng)建后虚婿,其他線程(比如main線程)調(diào)用了該對(duì)象的start()方法旋奢。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中然痊,獲取cpu 的使用權(quán) 至朗。
- 運(yùn)行(RUNNING):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu 時(shí)間片(timeslice) ,執(zhí)行程序代碼剧浸。
-
- 阻塞(BLOCKED):阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了cpu 使用權(quán)锹引,也即讓出了cpu timeslice,暫時(shí)停止運(yùn)行唆香。直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài)嫌变,才有機(jī)會(huì)再次獲得cpu timeslice 轉(zhuǎn)到運(yùn)行(running)狀態(tài)。阻塞的情況分三種:
- (一). 等待阻塞:運(yùn)行(running)的線程執(zhí)行o.wait()方法躬它,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中腾啥。
- (二). 同步阻塞:運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用冯吓,則JVM會(huì)把該線程放入鎖池(lock pool)中倘待。
- (三). 其他阻塞:運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請(qǐng)求時(shí)桑谍,JVM會(huì)把該線程置為阻塞狀態(tài)延柠。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)锣披、或者I/O處理完畢時(shí)贞间,線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。
- 死亡(DEAD):線程run()雹仿、main() 方法執(zhí)行結(jié)束增热,或者因異常退出了run()方法,則該線程結(jié)束生命周期胧辽。死亡的線程不可再次復(fù)生峻仇。
41. sleep() 和 wait() 有什么區(qū)別?
1邑商、這兩個(gè)方法來(lái)自不同的類分別是摄咆,sleep來(lái)自Thread類凡蚜,和wait來(lái)自O(shè)bject類。
2吭从、sleep() 和 wait() 的區(qū)別就是 調(diào)用sleep方法的線程不會(huì)釋放對(duì)象鎖朝蜘,而調(diào)用wait() 方法會(huì)釋放對(duì)象鎖
sleep是Thread的靜態(tài)類方法,誰(shuí)調(diào)用的誰(shuí)去睡覺(jué)涩金,即使在a線程里調(diào)用了b的sleep方法谱醇,實(shí)際上還是a去睡覺(jué),要讓b線程睡覺(jué)要在b的代碼中調(diào)用sleep步做。
wait()是Object類的方法副渴,當(dāng)一個(gè)線程執(zhí)行到wait方法時(shí),它就進(jìn)入到一個(gè)和該對(duì)象相關(guān)的等待池全度,同時(shí)釋放對(duì)象的機(jī)鎖煮剧,使得其他線程能夠訪問(wèn),可以通過(guò)notify讼载,notifyAll方法來(lái)喚醒等待的線程
sleep不出讓系統(tǒng)資源轿秧;wait是進(jìn)入線程等待池等待中跌,出讓系統(tǒng)資源咨堤,其他線程可以占用CPU。一般wait不會(huì)加時(shí)間限制漩符,因?yàn)槿绻鹷ait線程的運(yùn)行資源不夠一喘,再出來(lái)也沒(méi)用,要等待其他線程調(diào)用notify/notifyAll喚醒等待池中的所有線程嗜暴,才會(huì)進(jìn)入就緒隊(duì)列等待OS分配系統(tǒng)資源。sleep(milliseconds)可以用時(shí)間指定使它自動(dòng)喚醒過(guò)來(lái),如果時(shí)間不到只能調(diào)用interrupt()強(qiáng)行打斷锡宋。
Thread.Sleep(0)的作用是“觸發(fā)操作系統(tǒng)立刻重新進(jìn)行一次CPU競(jìng)爭(zhēng)”瘫证。
3、使用范圍:wait舆逃,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用蚂维,而sleep可以在任何地方使用
synchronized(x){
x.notify()
//或者wait()
}4、sleep必須捕獲異常路狮,而wait虫啥,notify和notifyAll不需要捕獲異常
42. notify()和 notifyAll()有什么區(qū)別?
如果線程調(diào)用了對(duì)象的 wait()方法奄妨,那么線程便會(huì)處于該對(duì)象的等待池中涂籽,等待池中的線程不會(huì)去競(jìng)爭(zhēng)該對(duì)象的鎖。
當(dāng)有線程調(diào)用了對(duì)象的 notifyAll()方法(喚醒所有 wait 線程)或 notify()方法(只隨機(jī)喚醒一個(gè) wait 線程)砸抛,被喚醒的的線程便會(huì)進(jìn)入該對(duì)象的鎖池中评雌,鎖池中的線程會(huì)去競(jìng)爭(zhēng)該對(duì)象鎖树枫。也就是說(shuō),調(diào)用了notify后只要一個(gè)線程會(huì)由等待池進(jìn)入鎖池景东,而notifyAll會(huì)將該對(duì)象等待池內(nèi)的所有線程移動(dòng)到鎖池中团赏,等待鎖競(jìng)爭(zhēng)
優(yōu)先級(jí)高的線程競(jìng)爭(zhēng)到對(duì)象鎖的概率大,假若某線程沒(méi)有競(jìng)爭(zhēng)到該對(duì)象鎖耐薯,它還會(huì)留在鎖池中舔清,唯有線程再次調(diào)用 wait()方法,它才會(huì)重新回到等待池中曲初。而競(jìng)爭(zhēng)到對(duì)象鎖的線程則繼續(xù)往下執(zhí)行体谒,直到執(zhí)行完了 synchronized 代碼塊,它會(huì)釋放掉該對(duì)象鎖臼婆,這時(shí)鎖池中的線程會(huì)繼續(xù)競(jìng)爭(zhēng)該對(duì)象鎖抒痒。
綜上,所謂喚醒線程颁褂,另一種解釋可以說(shuō)是將線程由等待池移動(dòng)到鎖池故响,notifyAll調(diào)用后,會(huì)將全部線程由等待池移到鎖池颁独,然后參與鎖的競(jìng)爭(zhēng)彩届,競(jìng)爭(zhēng)成功則繼續(xù)執(zhí)行,如果不成功則留在鎖池等待鎖被釋放后再次參與競(jìng)爭(zhēng)誓酒。而notify只會(huì)喚醒一個(gè)線程樟蠕。
notify可能會(huì)導(dǎo)致死鎖,而notifyAll則不會(huì)
43. 線程的 run()和 start()有什么區(qū)別靠柑?
每個(gè)線程都有要執(zhí)行的任務(wù)寨辩。線程的任務(wù)處理邏輯可以在Tread類的run實(shí)例方法中直接實(shí)現(xiàn)或通過(guò)該方法進(jìn)行調(diào)用,因此
run()相當(dāng)于線程的任務(wù)處理邏輯的入口方法歼冰,它由Java虛擬機(jī)在運(yùn)行相應(yīng)線程時(shí)直接調(diào)用靡狞,而不是由應(yīng)用代碼進(jìn)行調(diào)用。
start()的作用是啟動(dòng)相應(yīng)的線程隔嫡。啟動(dòng)一個(gè)線程實(shí)際是請(qǐng)求Java虛擬機(jī)運(yùn)行相應(yīng)的線程甸怕,而這個(gè)線程何時(shí)能夠運(yùn)行是由線程調(diào)度器決定的。start()調(diào)用結(jié)束并不表示相應(yīng)線程已經(jīng)開(kāi)始運(yùn)行畔勤,這個(gè)線程可能稍后運(yùn)行蕾各,也可能永遠(yuǎn)也不會(huì)運(yùn)行。
44.創(chuàng)建線程池有哪幾種方式庆揪?
Java通過(guò)Executors提供四種線程池式曲,分別為:
- newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程吝羞,若無(wú)可回收兰伤,則新建線程。
- newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池钧排,可控制線程最大并發(fā)數(shù)敦腔,超出的線程會(huì)在隊(duì)列中等待。
- newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池恨溜,支持定時(shí)及周期性任務(wù)執(zhí)行符衔。
- newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù)糟袁,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行判族。
阿里的 Java開(kāi)發(fā)手冊(cè),上面有線程池的一個(gè)建議:線程池不允許使用 Executors 去創(chuàng)建项戴,而是通過(guò) ThreadPoolExecutor 的方式形帮,這樣的處理方式讓寫(xiě)的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)周叮。Executors利用工廠模式向我們提供了4種線程池實(shí)現(xiàn)方式,但是并不推薦使用,原因是使用Executors創(chuàng)建線程池不會(huì)傳入拒絕策略這個(gè)參數(shù)而使用默認(rèn)值所以我們常常忽略這一參數(shù),而且默認(rèn)使用的參數(shù)會(huì)導(dǎo)致資源浪費(fèi),不可取辩撑。
說(shuō)明:Executors 各個(gè)方法的弊端:
1)newFixedThreadPool 和 newSingleThreadExecutor:主要問(wèn)題是堆積的請(qǐng)求處理隊(duì)列可能會(huì)耗費(fèi)非常大的內(nèi)存,甚至 OOM仿耽。
2)newCachedThreadPool 和 newScheduledThreadPool:主要問(wèn)題是線程數(shù)最大數(shù)是 Integer.MAX_VALUE合冀,可能會(huì)創(chuàng)建數(shù)量非常多的線程,甚至 OOM氓仲。
46. 線程池中 submit()和 execute()方法有什么區(qū)別水慨?
- 接收的參數(shù)不一樣;submit callable,execute 是runnable
- submit()有返回值敬扛,而execute()沒(méi)有;
例如,有個(gè)validation的task朝抖,希望該task執(zhí)行完后告訴我它的執(zhí)行結(jié)果啥箭,是成功還是失敗,然后繼續(xù)下面的操作治宣。
- submit()有返回值敬扛,而execute()沒(méi)有;
- submit()可以進(jìn)行Exception處理;
例如急侥,如果task里會(huì)拋出checked或者unchecked exception,而你又希望外面的調(diào)用者能夠感知這些exception并做出及時(shí)的處理侮邀,那么就需要用到submit坏怪,通過(guò)對(duì)Future.get()進(jìn)行拋出異常的捕獲,然后對(duì)其進(jìn)行處理绊茧。
- submit()可以進(jìn)行Exception處理;
47. 在 java 程序中怎么保證多線程的運(yùn)行安全铝宵?
一、線程安全在三個(gè)方面體現(xiàn)
1.原子性:提供互斥訪問(wèn),同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作鹏秋,(atomic,synchronized)尊蚁;
2.可見(jiàn)性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)地被其他線程看到,(synchronized,volatile)侣夷;
3.有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序横朋,由于指令重排序,該觀察結(jié)果一般雜亂無(wú)序百拓,(happens-before原則)琴锭。
當(dāng)然由于synchronized和Lock保證每個(gè)時(shí)刻只有一個(gè)線程執(zhí)行同步代碼,所以是線程安全的衙传,也可以實(shí)現(xiàn)這一功能祠够,但是由于線程是同步執(zhí)行的,所以會(huì)影響效率粪牲。
原子性:即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷古瓤,要么就都不執(zhí)行。JDK里面提供了很多atomic類腺阳,AtomicInteger,AtomicLong,AtomicBoolean等等落君。它們是通過(guò)CAS完成原子性
可見(jiàn)性:指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值亭引,其他線程能夠立即看得到修改的值绎速。
當(dāng)一個(gè)共享變量被volatile修飾時(shí),它會(huì)保證修改的值會(huì)立即被更新到主存焙蚓,當(dāng)有其他線程需要讀取共享變量時(shí)纹冤,它會(huì)去內(nèi)存中讀取新值。
普通的共享變量不能保證可見(jiàn)性购公,因?yàn)槠胀ü蚕碜兞勘恍薷暮竺染裁磿r(shí)候被寫(xiě)入主存是不確定的,當(dāng)其他線程去讀取時(shí)宏浩,此時(shí)內(nèi)存中可能還是原來(lái)的舊值知残,因此無(wú)法保證可見(jiàn)性。
更新主存的步驟:當(dāng)前線程將其他線程的工作內(nèi)存中的緩存變量的緩存行設(shè)置為無(wú)效比庄,然后當(dāng)前線程將變量的值跟新到主存求妹,更新成功后將其他線程的緩存行更新為新的主存地址
其他線程讀取變量時(shí),發(fā)現(xiàn)自己的緩存行無(wú)效佳窑,它會(huì)等待緩存行對(duì)應(yīng)的主存地址被更新之后制恍,然后去對(duì)應(yīng)的主存讀取最新的值。
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行神凑。
在Java內(nèi)存模型中净神,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性强挫。
可以通過(guò)volatile關(guān)鍵字來(lái)保證一定的“有序性”岔霸。
48. 多線程鎖的升級(jí)原理是什么?
在Java中俯渤,鎖共有4種狀態(tài)呆细,級(jí)別從低到高依次為:無(wú)狀態(tài)鎖,偏向鎖八匠,輕量級(jí)鎖和重量級(jí)鎖狀態(tài)絮爷,這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí)梨树。
偏向鎖
偏向鎖的核心思想就是鎖會(huì)偏向第一個(gè)獲取它的線程坑夯,在接下來(lái)的執(zhí)行過(guò)程中該鎖沒(méi)有其他的線程獲取,則持有偏向鎖的線程永遠(yuǎn)不需要再進(jìn)行同步抡四。
當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖的時(shí)候柜蜈,會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)偏向的線程 ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行 CAS 操作來(lái)加鎖和解鎖指巡,只需要檢查當(dāng)前 Mark Word 中存儲(chǔ)的線程是否為當(dāng)前線程淑履,如果是,則表示已經(jīng)獲得對(duì)象鎖藻雪;否則秘噪,需要測(cè)試 Mark Word 中偏向鎖的標(biāo)志是否為1,如果沒(méi)有則使用 CAS 操作競(jìng)爭(zhēng)鎖勉耀,如果設(shè)置了指煎,則嘗試使用 CAS 將對(duì)象頭的偏向鎖指向當(dāng)前線程。
需要注意的是便斥,偏向鎖使用一種等待競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制至壤,所以當(dāng)有其他線程嘗試獲得鎖時(shí),才會(huì)釋放鎖椭住。偏向鎖的撤銷崇渗,需要等到安全點(diǎn)。它首先會(huì)暫停擁有偏向鎖的線程京郑,然后檢查持有偏向鎖的線程是否活著,如果不處于活動(dòng)狀態(tài)葫掉,則將對(duì)象頭設(shè)置為無(wú)鎖狀態(tài)些举;如果依然活動(dòng),擁有偏向鎖的棧會(huì)被執(zhí)行俭厚,遍歷偏向?qū)ο蟮逆i記錄户魏,棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向其他線程,要么恢復(fù)到無(wú)鎖或者標(biāo)記對(duì)象不合適作為偏向鎖(膨脹為輕量級(jí)鎖),最后喚醒暫停的線程叼丑。
輕量級(jí)鎖
線程在執(zhí)行同步塊之前关翎,JVM會(huì)現(xiàn)在當(dāng)前線程的棧幀中創(chuàng)建用于儲(chǔ)存鎖記錄的空間(LockRecord),并將對(duì)象頭的Mark Word信息復(fù)制到鎖記錄中鸠信。然后線程嘗試使用 CAS 將對(duì)象頭的MarkWord替換為指向鎖記錄的指針纵寝。如果成功,當(dāng)前線程獲得鎖星立,并且對(duì)象的鎖標(biāo)志位轉(zhuǎn)變?yōu)椤?0”爽茴,如果失敗,表示其他線程競(jìng)爭(zhēng)鎖绰垂,當(dāng)前線程便會(huì)嘗試自旋獲取鎖室奏。如果有兩條以上的線程競(jìng)爭(zhēng)同一個(gè)鎖,那么輕量級(jí)鎖就不再有效劲装,要膨脹為重量級(jí)鎖胧沫,鎖標(biāo)志的狀態(tài)變?yōu)椤?0”,MarkWord中儲(chǔ)存的就是指向重量級(jí)鎖(互斥量)的指針占业,后面等待的線程也要進(jìn)入阻塞狀態(tài)绒怨。
輕量級(jí)鎖解鎖時(shí),同樣通過(guò)CAS操作將對(duì)象頭換回來(lái)纺酸。如果成功窖逗,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗餐蔬,說(shuō)明有其他線程嘗試過(guò)獲取該鎖碎紊,鎖同樣會(huì)膨脹為重量級(jí)鎖。在釋放鎖的同時(shí)樊诺,喚醒被掛起的線程仗考。
重量級(jí)鎖
重量級(jí)鎖(Heavyweight Lock)是將程序運(yùn)行交出控制權(quán),將線程掛起词爬,由操作系統(tǒng)來(lái)負(fù)責(zé)線程間的調(diào)度秃嗜,負(fù)責(zé)線程的阻塞和執(zhí)行。這樣會(huì)出現(xiàn)頻繁地對(duì)線程運(yùn)行狀態(tài)的切換顿膨,線程的掛起和喚醒锅锨,消耗大量的系統(tǒng)資源,導(dǎo)致性能低下恋沃。
最后看一下必搞,鎖升級(jí)的圖示過(guò)程:
[圖片上傳失敗...(image-d9e059-1557830709655)]
49. 什么是死鎖?
死鎖可以這樣理解囊咏,就是互相不讓步不放棄恕洲,同時(shí)需要對(duì)方的資源塔橡。造成互相不滿足資源需求,也不放棄自身已有資源霜第。死鎖就這樣了葛家。
死鎖是指多個(gè)進(jìn)程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待),若無(wú)外力作用泌类,這些進(jìn)程都將無(wú)法向前推進(jìn)癞谒。
死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去,如果系統(tǒng)資源充足末誓,進(jìn)程的資源請(qǐng)求都能夠得到滿足扯俱,死鎖出現(xiàn)的可能性就很低,否則就會(huì)因爭(zhēng)奪有限的資源而陷入死鎖喇澡。
產(chǎn)生死鎖的原因主要是:
⊙刚ぁ(1) 因?yàn)橄到y(tǒng)資源不足。
∏缇痢(2) 進(jìn)程運(yùn)行推進(jìn)的順序不合適读存。
(3) 資源分配不當(dāng)?shù)取?br>
產(chǎn)生死鎖的四個(gè)必要條件:
∨皇骸(1) 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用让簿。
(2) 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí)秀睛,對(duì)已獲得的資源保持不放尔当。
(3) 不剝奪條件:進(jìn)程已獲得的資源蹂安,在末使用完之前椭迎,不能強(qiáng)行剝奪。
√镉(4) 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系畜号。
這四個(gè)條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖允瞧,這些條件必然成立简软,而只要上述條件之
一不滿足,就不會(huì)發(fā)生死鎖述暂。
50. 怎么防止死鎖痹升?
理解了死鎖的原因,尤其是產(chǎn)生死鎖的四個(gè)必要條件畦韭,就可以最大可能地避免视卢、預(yù)防和解除死鎖。所以廊驼,在系統(tǒng)設(shè)計(jì)据过、進(jìn)程調(diào)度等方面注意如何不讓這四個(gè)必要條件成立,如何確定資源的合理分配算法妒挎,避免進(jìn)程永久占據(jù)系統(tǒng)資源绳锅。此外,也要防止進(jìn)程在處于等待狀態(tài)
的情況下占用資源酝掩。因此鳞芙,對(duì)資源的分配要給予合理的規(guī)劃。
預(yù)防死鎖期虾,預(yù)先破壞產(chǎn)生死鎖的四個(gè)條件原朝。互斥不可能破壞镶苞,所以有如下三種方法:
1喳坠、破壞請(qǐng)求和保持條件,
進(jìn)程必須等所有要請(qǐng)求的資源都空閑時(shí)才能申請(qǐng)資源茂蚓,這種方法會(huì)使資源浪費(fèi)嚴(yán)重(有些資源可能僅在運(yùn)行初期或結(jié)束時(shí)才使用壕鹉,甚至根本不使用)。
允許進(jìn)程獲取初期所需資源后聋涨,便開(kāi)始運(yùn)行晾浴,運(yùn)行過(guò)程中再逐步釋放自己占有的資源,比如有一個(gè)進(jìn)程的任務(wù)是把數(shù)據(jù)復(fù)制到磁盤中再打印牍白,前期只需獲得磁盤資源而不需要獲得打印機(jī)資源脊凰,待復(fù)制完畢后再釋放掉磁盤資源。這種方法比第一種方法好茂腥,會(huì)使資源利用率上升狸涌。
2、破壞不可搶占條件
這種方法代價(jià)大础芍,實(shí)現(xiàn)復(fù)雜杈抢。
3、破壞循壞等待條件
對(duì)各進(jìn)程請(qǐng)求資源的順序做一個(gè)規(guī)定仑性,避免相互等待惶楼。這種方法對(duì)資源的利用率比前兩種都高,但是前期要為設(shè)備指定序號(hào)诊杆,新設(shè)備加入會(huì)有一個(gè)問(wèn)題歼捐,其次對(duì)用戶編程也有限制。
死鎖晨汹,基本就是資源不夠豹储,互相需要對(duì)方資源卻不肯放棄自身資源。N線程訪問(wèn)N資源淘这,為了避免死鎖剥扣,可以為其加鎖并指定獲取鎖的順序巩剖,這樣線程按照順序加鎖訪問(wèn)資源,依次使用依次釋放钠怯,可以避免死鎖佳魔。
使用多線程的時(shí)候,一種非常簡(jiǎn)單的避免死鎖的方式就是:指定獲取鎖的順序晦炊,并強(qiáng)制線程按照指定的順序獲取鎖鞠鲜。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖断国,就不會(huì)出現(xiàn)死鎖了贤姆。可以確保N個(gè)線程可以訪問(wèn)N個(gè)資源同時(shí)又不導(dǎo)致死鎖了稳衬。
51. ThreadLocal 是什么霞捡?有哪些使用場(chǎng)景?
ThreadLocal宋彼,即線程變量弄砍,是一個(gè)以 ThreadLocal 對(duì)象為鍵、任意對(duì)象為值的存儲(chǔ)結(jié)構(gòu)输涕。
ThreadLocal是各線程將值存入該線程的map中音婶,以ThreadLocal自身作為key,需要用時(shí)獲得的是該線程之前存入的值莱坎。如果存入的是共享變量衣式,那取出的也是共享變量,并發(fā)問(wèn)題還是存在的檐什。
ThreadLocal是用來(lái)維護(hù)本線程的變量的碴卧,并不能解決共享變量的并發(fā)問(wèn)題。
ThreadLocal既然不能解決并發(fā)問(wèn)題乃正,那么它適用的場(chǎng)景是什么呢住册?
ThreadLocal的主要用途是為了保持線程自身對(duì)象和避免參數(shù)傳遞,主要適用場(chǎng)景是按線程多實(shí)例(每個(gè)線程對(duì)應(yīng)一個(gè)實(shí)例)的對(duì)象的訪問(wèn)瓮具,并且這個(gè)對(duì)象很多地方都要用到荧飞。
最常見(jiàn)的ThreadLocal使用場(chǎng)景為 用來(lái)解決數(shù)據(jù)庫(kù)連接、Session管理等名党。如:
#數(shù)據(jù)庫(kù)連接:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
#Session管理:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
個(gè)人認(rèn)為使用ThreadLocal的場(chǎng)景最好滿足兩個(gè)條件叹阔,一是該對(duì)象不需要在多線程之間共享;二是該對(duì)象需要在線程內(nèi)被傳遞
52. 說(shuō)一下 synchronized 底層實(shí)現(xiàn)原理传睹?
synchronized的語(yǔ)義底層是通過(guò)一個(gè)monitor的對(duì)象來(lái)完成耳幢。
其實(shí)wait/notify等方法也依賴于monitor對(duì)象,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法欧啤,否則會(huì)拋出java.lang.IllegalMonitorStateException的異常的原因睛藻。
涉及兩條指令:(1)monitorenter
每個(gè)對(duì)象有一個(gè)監(jiān)視器鎖(monitor)启上。當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán)修档,過(guò)程如下:
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)口柳。
(2)monitorexit
執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者。
指令執(zhí)行時(shí)有滑,monitor的進(jìn)入數(shù)減1跃闹,如果減1后進(jìn)入數(shù)為0,那線程退出monitor毛好,不再是這個(gè)monitor的所有者望艺。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè)
monitor 的所有權(quán)。
53. synchronized 和 volatile 的區(qū)別是什么肌访?
首先需要理解線程安全的兩個(gè)方面:執(zhí)行控制和內(nèi)存可見(jiàn)找默。
執(zhí)行控制的目的是控制代碼執(zhí)行(順序)及是否可以并發(fā)執(zhí)行。
內(nèi)存可見(jiàn)控制的是線程執(zhí)行結(jié)果在內(nèi)存中對(duì)其它線程的可見(jiàn)性吼驶。根據(jù)Java內(nèi)存模型的實(shí)現(xiàn)惩激,線程在具體執(zhí)行時(shí),會(huì)先拷貝主存數(shù)據(jù)到線程本地(CPU緩存)旨剥,操作完成后再把結(jié)果從線程本地刷到主存咧欣。
synchronized
關(guān)鍵字解決的是執(zhí)行控制的問(wèn)題,它會(huì)阻止其它線程獲取當(dāng)前對(duì)象的監(jiān)控鎖轨帜,這樣就使得當(dāng)前對(duì)象中被synchronized
關(guān)鍵字保護(hù)的代碼塊無(wú)法被其它線程訪問(wèn)魄咕,也就無(wú)法并發(fā)執(zhí)行。更重要的是蚌父,synchronized
還會(huì)創(chuàng)建一個(gè)內(nèi)存屏障哮兰,內(nèi)存屏障指令保證了所有CPU操作結(jié)果都會(huì)直接刷到主存中毛萌,從而保證了操作的內(nèi)存可見(jiàn)性,同時(shí)也使得先獲得這個(gè)鎖的線程的所有操作喝滞,都happens-before于隨后獲得這個(gè)鎖的線程的操作阁将。
volatile
關(guān)鍵字解決的是內(nèi)存可見(jiàn)性的問(wèn)題,會(huì)使得所有對(duì)volatile
變量的讀寫(xiě)都會(huì)直接刷到主存右遭,即保證了變量的可見(jiàn)性做盅。這樣就能滿足一些對(duì)變量可見(jiàn)性有要求而對(duì)讀取順序沒(méi)有要求的需求。
區(qū)別
volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的窘哈,需要從主存中讀却盗瘛; synchronized則是鎖定當(dāng)前變量滚婉,只有當(dāng)前線程可以訪問(wèn)該變量图筹,其他線程被阻塞住。
volatile僅能使用在變量級(jí)別让腹;synchronized則可以使用在變量远剩、方法、和類級(jí)別的
volatile僅能實(shí)現(xiàn)變量的修改可見(jiàn)性骇窍,不能保證原子性瓜晤;而synchronized則可以保證變量的修改可見(jiàn)性和原子性
volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞像鸡。
volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化活鹰;synchronized標(biāo)記的變量可以被編譯器優(yōu)化
54. synchronized 和 Lock 有什么區(qū)別?
1)Lock是一個(gè)接口只估,而synchronized是Java中的關(guān)鍵字志群,synchronized是內(nèi)置的語(yǔ)言實(shí)現(xiàn);
2)synchronized在發(fā)生異常時(shí)蛔钙,會(huì)自動(dòng)釋放線程占有的鎖锌云,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時(shí)吁脱,如果沒(méi)有主動(dòng)通過(guò)unLock()去釋放鎖桑涎,則很可能造成死鎖現(xiàn)象,因此使用Lock時(shí)需要在finally塊中釋放鎖兼贡;
3)Lock可以讓等待鎖的線程響應(yīng)中斷攻冷,而synchronized卻不行,使用synchronized時(shí)遍希,等待的線程會(huì)一直等待下去等曼,不能夠響應(yīng)中斷;
4)通過(guò)Lock可以知道有沒(méi)有成功獲取鎖,而synchronized卻無(wú)法辦到禁谦。
5)Lock可以提高多個(gè)線程進(jìn)行讀操作的效率胁黑。
在性能上來(lái)說(shuō),如果競(jìng)爭(zhēng)資源不激烈州泊,兩者的性能是差不多的丧蘸,而當(dāng)競(jìng)爭(zhēng)資源非常激烈時(shí)(即有大量線程同時(shí)競(jìng)爭(zhēng)),此時(shí)Lock的性能要遠(yuǎn)遠(yuǎn)優(yōu)于synchronized遥皂。所以說(shuō)力喷,在具體使用時(shí)要根據(jù)適當(dāng)情況選擇。
55. synchronized 和 ReentrantLock 區(qū)別是什么渴肉?
ReenTrantLock可重入鎖(和synchronized的區(qū)別)總結(jié)
可重入性:
從名字上理解冗懦,ReenTrantLock的字面意思就是再進(jìn)入的鎖,其實(shí)synchronized關(guān)鍵字所使用的鎖也是可重入的仇祭,兩者關(guān)于這個(gè)的區(qū)別不大。兩者都是同一個(gè)線程沒(méi)進(jìn)入一次颈畸,鎖的計(jì)數(shù)器都自增1乌奇,所以要等到鎖的計(jì)數(shù)器下降為0時(shí)才能釋放鎖。
鎖的實(shí)現(xiàn):
Synchronized是依賴于JVM實(shí)現(xiàn)的眯娱,而ReenTrantLock是JDK實(shí)現(xiàn)的礁苗,有什么區(qū)別,說(shuō)白了就類似于操作系統(tǒng)來(lái)控制實(shí)現(xiàn)和用戶自己敲代碼實(shí)現(xiàn)的區(qū)別徙缴。前者的實(shí)現(xiàn)是比較難見(jiàn)到的试伙,后者有直接的源碼可供閱讀。
性能的區(qū)別:
在Synchronized優(yōu)化以前于样,synchronized的性能是比ReenTrantLock差很多的疏叨,但是自從Synchronized引入了偏向鎖,輕量級(jí)鎖(自旋鎖)后穿剖,兩者的性能就差不多了蚤蔓,在兩種方法都可用的情況下,官方甚至建議使用synchronized糊余,其實(shí)synchronized的優(yōu)化我感覺(jué)就借鑒了ReenTrantLock中的CAS技術(shù)秀又。都是試圖在用戶態(tài)就把加鎖問(wèn)題解決,避免進(jìn)入內(nèi)核態(tài)的線程阻塞贬芥。
功能區(qū)別:
便利性:很明顯Synchronized的使用比較方便簡(jiǎn)潔吐辙,并且由編譯器去保證鎖的加鎖和釋放,而ReenTrantLock需要手工聲明來(lái)加鎖和釋放鎖蘸劈,為了避免忘記手工釋放鎖造成死鎖昏苏,所以最好在finally中聲明釋放鎖。
鎖的細(xì)粒度和靈活度:很明顯ReenTrantLock優(yōu)于Synchronized
ReenTrantLock獨(dú)有的能力:
ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖捷雕。所謂的公平鎖就是先等待的線程先獲得鎖椒丧。
ReenTrantLock提供了一個(gè)Condition(條件)類,用來(lái)實(shí)現(xiàn)分組喚醒需要喚醒的線程們救巷,而不是像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程壶熏。
ReenTrantLock提供了一種能夠中斷等待鎖的線程的機(jī)制,通過(guò)lock.lockInterruptibly()來(lái)實(shí)現(xiàn)這個(gè)機(jī)制浦译。
ReenTrantLock實(shí)現(xiàn)的原理:
簡(jiǎn)單來(lái)說(shuō)棒假,ReenTrantLock的實(shí)現(xiàn)是一種自旋鎖,通過(guò)循環(huán)調(diào)用CAS操作來(lái)實(shí)現(xiàn)加鎖精盅。它的性能比較好也是因?yàn)楸苊饬耸咕€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)帽哑。想盡辦法避免線程進(jìn)入內(nèi)核的阻塞狀態(tài)是我們?nèi)シ治龊屠斫怄i設(shè)計(jì)的關(guān)鍵鑰匙。
56. 說(shuō)一下 atomic 的原理叹俏?
在多線程的場(chǎng)景中妻枕,我們需要保證數(shù)據(jù)安全,就會(huì)考慮同步的方案粘驰,通常會(huì)使用synchronized或者lock來(lái)處理屡谐,使用了synchronized意味著內(nèi)核態(tài)的一次切換。這是一個(gè)很重的操作蝌数。
有沒(méi)有一種方式愕掏,可以比較便利的實(shí)現(xiàn)一些簡(jiǎn)單的數(shù)據(jù)同步,比如計(jì)數(shù)器等等顶伞。concurrent包下的atomic提供我們這么一種輕量級(jí)的數(shù)據(jù)同步的選擇饵撑。
優(yōu)缺點(diǎn)
CAS相對(duì)于其他鎖,不會(huì)進(jìn)行內(nèi)核態(tài)操作唆貌,有著一些性能的提升滑潘。但同時(shí)引入自旋,當(dāng)鎖競(jìng)爭(zhēng)較大的時(shí)候挠锥,自旋次數(shù)會(huì)增多众羡。cpu資源會(huì)消耗很高。
換句話說(shuō)蓖租,CAS+自旋適合使用在低并發(fā)有同步數(shù)據(jù)的應(yīng)用場(chǎng)景粱侣。
作者:張凱_9908
鏈接:http://www.reibang.com/p/b0ce8f034ab3
來(lái)源:簡(jiǎn)書(shū)
簡(jiǎn)書(shū)著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處蓖宦。