一精耐、實(shí)現(xiàn)方式
線程的實(shí)現(xiàn)有兩種方式,一是繼承Thread類克饶,二是實(shí)現(xiàn)Runnable接口酝蜒。
二、狀態(tài)
1.新建狀態(tài)
創(chuàng)建了一個(gè)線程對象矾湃。
2.就緒狀態(tài)
線程對象創(chuàng)建后亡脑,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中邀跃,變得可運(yùn)行霉咨,等待獲取CPU的使用權(quán)。
3.運(yùn)行狀態(tài)
就緒狀態(tài)的線程獲取了CPU拍屑,執(zhí)行run()方法躯护。
4.阻塞狀態(tài)
阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行丽涩。直到線程進(jìn)入就緒狀態(tài)棺滞,才有機(jī)會轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
a.等待阻塞
運(yùn)行的線程執(zhí)行wait()方法矢渊,JVM會把該線程放入等待池中继准。
b.同步阻塞
運(yùn)行的線程在獲取對象的同步鎖時(shí),若該同步鎖被別的線程占用矮男,則JVM會把該線程放入鎖池中移必。
c.其他阻塞
運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時(shí)毡鉴,JVM會把該線程置為阻塞狀態(tài)崔泵。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)猪瞬、或者I/O處理完畢時(shí)憎瘸,線程重新轉(zhuǎn)入就緒狀態(tài)。
5.死亡狀態(tài)
線程執(zhí)行完了或者因異常退出了run()方法陈瘦,該線程結(jié)束生命周期幌甘。
三、run和start的區(qū)別
當(dāng)調(diào)用start方法的時(shí)候痊项,該線程就進(jìn)入就緒狀態(tài)锅风。等待CPU進(jìn)行調(diào)度執(zhí)行,此時(shí)還沒有真正執(zhí)行線程鞍泉。
當(dāng)調(diào)用run方法的時(shí)候皱埠,是已經(jīng)被CPU進(jìn)行調(diào)度,執(zhí)行線程的主要任務(wù)咖驮。
四边器、wait泪姨,join,sleep饰抒,yield, notify,notifyall诀黍,synchronized相關(guān)釋義
1.wait
在其他線程調(diào)用對象的notify或notifyAll方法前袋坑,導(dǎo)致當(dāng)前線程等待。線程會釋放掉它所占有的“鎖標(biāo)志”眯勾,從而使別的線程有機(jī)會搶占該鎖枣宫。
喚醒當(dāng)前對象鎖的等待線程使用notify或notifyAll方法,wait() 和notify()必須在synchronized函數(shù)或synchronized block中進(jìn)行調(diào)用。
2.sleep
在指定時(shí)間內(nèi)讓當(dāng)前正在執(zhí)行的線程暫停執(zhí)行吃环,但不會釋放“鎖標(biāo)志”也颤。不推薦使用。sleep()使當(dāng)前線程進(jìn)入阻塞狀態(tài)郁轻,在指定時(shí)間內(nèi)不會執(zhí)行翅娶。
3.yield
暫停當(dāng)前正在執(zhí)行的線程對象。yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài)好唯,所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行竭沫。yield()只能使同優(yōu)先級或更高優(yōu)先級的線程有執(zhí)行的機(jī)會。
4.join
等待該線程終止骑篙。等待調(diào)用join方法的線程結(jié)束蜕提,再繼續(xù)執(zhí)行。如:在main線程中執(zhí)行t.join()靶端,主要用于等待t線程運(yùn)行結(jié)束谎势,若無此句,main則會執(zhí)行完畢杨名,導(dǎo)致結(jié)果不可預(yù)測脏榆。(join方法會釋放this對象的鎖)
五、sleep() 與 wait()的區(qū)別
1.有關(guān)類
這兩個(gè)方法來自不同的類分別是台谍,sleep來自Thread類姐霍,和wait來自O(shè)bject 類。
2.有關(guān)鎖
最主要是sleep方法沒有釋放鎖典唇,而wait方法釋放了鎖镊折,使得其他線程可以使用同步控制塊或者方法。
3.有關(guān)資源
sleep不出讓系統(tǒng)資源介衔;wait是進(jìn)入線程等待池等待恨胚,出讓系統(tǒng)資源,其他線程可以占用CPU炎咖。一般wait不會加時(shí)間限制赃泡,因?yàn)槿绻鹷ait線程的運(yùn)行資源不夠寒波,再出來也沒用,要等待其他線程調(diào)用notify/notifyAll喚醒等待池中的所有線程升熊,才會進(jìn)入就緒隊(duì)列等待OS分配系統(tǒng)資源俄烁。
4.有關(guān)喚醒
sleep(milliseconds)可以用時(shí)間指定使它自動喚醒過來,如果時(shí)間不到只能調(diào)用interrupt()強(qiáng)行打斷级野。
5.有關(guān)使用
wait页屠,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用
6.有關(guān)異常
sleep需要捕獲異常,而wait不需要
六蓖柔、ThreadLocal
1.定義
ThreadLocal 提供了線程本地的實(shí)例辰企。它與普通變量的區(qū)別在于,每個(gè)使用該變量的線程都會初始化一個(gè)完全獨(dú)立的實(shí)例副本况鸣。ThreadLocal 變量通常被private static修飾牢贸。當(dāng)一個(gè)線程結(jié)束時(shí),它所使用的所有 ThreadLocal 相對的實(shí)例副本都可被回收镐捧。
2.適用場景和總結(jié)
- 每個(gè)線程需要有自己單獨(dú)的實(shí)例
- 實(shí)例需要在多個(gè)方法中共享潜索,但不希望被多線程共享
- ThreadLocal 并不解決線程間共享數(shù)據(jù)的問題
- ThreadLocal 通過隱式的在不同線程內(nèi)創(chuàng)建獨(dú)立實(shí)例副本避免了實(shí)例線程安全的問題
- 每個(gè)線程持有一個(gè) Map 并維護(hù)了 ThreadLocal 對象與具體實(shí)例的映射,該 Map 由于只被持有它的線程訪問懂酱,故不存在線程安全以及鎖的問題
- ThreadLocalMap 的 Entry 對 ThreadLocal 的引用為弱引用帮辟,避免了 ThreadLocal 對象無法被回收的問題
- ThreadLocalMap 的 set 方法通過調(diào)用 replaceStaleEntry 方法回收鍵為 null 的 Entry 對象的值(即為具體實(shí)例)以及 Entry 對象本身從而防止內(nèi)存泄漏
- ThreadLocal 適用于變量在線程間隔離且在方法間共享的場景
七、線程安全
1.概念
-
原子性
一個(gè)操作(有可能包含有多個(gè)子操作)要么全部執(zhí)行(生效)玩焰,要么全部都不執(zhí)行(都不生效)由驹。
-
可見性
當(dāng)多個(gè)線程并發(fā)訪問共享變量時(shí),一個(gè)線程對共享變量的修改昔园,其它線程能夠立即看到蔓榄。
-
順序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。
2.如何解決多線程并發(fā)問題
-
如何保證原子性
a.鎖和同步
- 常用的保證Java操作原子性的工具是鎖和同步方法(或者同步代碼塊)默刚。使用鎖甥郑,可以保證同一時(shí)間只有一個(gè)線程能拿到鎖,也就保證了同一時(shí)間只有一個(gè)線程能執(zhí)行申請鎖和釋放鎖之間的代碼荤西。
- 與鎖類似的是同步方法或者同步代碼塊澜搅。使用非靜態(tài)同步方法時(shí),鎖住的是當(dāng)前實(shí)例邪锌;使用靜態(tài)同步方法時(shí)勉躺,鎖住的是該類的Class對象;使用靜態(tài)代碼塊時(shí)觅丰,鎖住的是synchronized關(guān)鍵字后面括號內(nèi)的對象饵溅。
- 無論使用鎖還是synchronized,本質(zhì)都是一樣妇萄,通過鎖來實(shí)現(xiàn)資源的排它性蜕企,從而實(shí)際目標(biāo)代碼段同一時(shí)間只會被一個(gè)線程執(zhí)行咬荷,進(jìn)而保證了目標(biāo)代碼段的原子性。這是一種以犧牲性能為代價(jià)的方法轻掩。
b.CAS(compare and swap)
基礎(chǔ)類型變量自增(i++)是一種常被誤以為是原子操作而實(shí)際不是的操作幸乒。Java中提供了對應(yīng)的原子操作類來實(shí)現(xiàn)該操作,并保證原子性唇牧,其本質(zhì)是利用了CPU級別的CAS指令罕扎。由于是CPU級別的指令,其開銷比需要操作系統(tǒng)參與的鎖的開銷小奋构。AtomicInteger使用方法如下。
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
new Thread(() -> {
for(int a = 0; a < iteration; a++) {
atomicInteger.incrementAndGet();
}
}).start();
}
-
如何保證可見性
Java提供了volatile關(guān)鍵字來保證可見性拱层。當(dāng)使用volatile修飾某個(gè)變量時(shí)弥臼,它會保證對該變量的修改會立即被更新到內(nèi)存中,并且將其它緩存中對該變量的緩存設(shè)置成無效根灯,因此其它線程需要讀取該值時(shí)必須從主內(nèi)存中讀取径缅,從而得到最新的值。
-
如何保證順序性
- Java中可通過volatile在一定程序上保證順序性烙肺,另外還可以通過synchronized和鎖來保證順序性纳猪。
- synchronized和鎖保證順序性的原理和保證原子性一樣,都是通過保證同一時(shí)間只會有一個(gè)線程執(zhí)行目標(biāo)代碼段來實(shí)現(xiàn)的桃笙。
- 除了從應(yīng)用層面保證目標(biāo)代碼段執(zhí)行的順序性外氏堤,JVM還通過被稱為happens-before原則隱式地保證順序性。兩個(gè)操作的執(zhí)行順序只要可以通過happens-before推導(dǎo)出來搏明,則JVM會保證其順序性鼠锈,反之JVM對其順序性不作任何保證,可對其進(jìn)行任意必要的重新排序以獲取高效率星著。
happens-before原則(先行發(fā)生原則)
- 傳遞規(guī)則:如果操作1在操作2前面购笆,而操作2在操作3前面,則操作1肯定會在操作3前發(fā)生虚循。該規(guī)則說明了happens-before原則具有傳遞性
- 鎖定規(guī)則:一個(gè)unlock操作肯定會在后面對同一個(gè)鎖的lock操作前發(fā)生同欠。這個(gè)很好理解,鎖只有被釋放了才會被再次獲取
- volatile變量規(guī)則:對一個(gè)被volatile修飾的寫操作先發(fā)生于后面對該變量的讀操作
- 程序次序規(guī)則:一個(gè)線程內(nèi)横缔,按照代碼順序執(zhí)行
- 線程啟動規(guī)則:Thread對象的start()方法先發(fā)生于此線程的其它動作
- 線程終結(jié)原則:線程的終止檢測后發(fā)生于線程中其它的所有操作
- 線程中斷規(guī)則: 對線程interrupt()方法的調(diào)用先發(fā)生于對該中斷異常的獲取
- 對象終結(jié)規(guī)則:一個(gè)對象構(gòu)造先于它的finalize發(fā)生
3.volatile適用場景
volatile適用于不需要保證原子性铺遂,但卻需要保證可見性的場景。一種典型的使用場景是用它修飾用于停止線程的狀態(tài)標(biāo)記茎刚。如下所示
boolean isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;
}
在這種實(shí)現(xiàn)方式下娃循,即使其它線程通過調(diào)用stop()方法將isRunning設(shè)置為false,循環(huán)也不一定會立即結(jié)束斗蒋“聘可以通過volatile關(guān)鍵字笛质,保證while循環(huán)及時(shí)得到isRunning最新的狀態(tài)從而及時(shí)停止循環(huán),結(jié)束線程捞蚂。
4.相關(guān)問題
問:平時(shí)項(xiàng)目中使用鎖和synchronized比較多妇押,而很少使用volatile,難道就沒有保證可見性姓迅?
答:鎖和synchronized即可以保證原子性敲霍,也可以保證可見性。都是通過保證同一時(shí)間只有一個(gè)線程執(zhí)行目標(biāo)代碼段來實(shí)現(xiàn)的丁存。
問:鎖和synchronized為何能保證可見性肩杈?
答:根據(jù)JDK 7的Java doc中對concurrent
包的說明,一個(gè)線程的寫結(jié)果保證對另外線程的讀操作可見解寝,只要該寫操作可以由happen-before
原則推斷出在讀操作之前發(fā)生扩然。
The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships.
問:既然鎖和synchronized即可保證原子性也可保證可見性,為何還需要volatile聋伦?
答:synchronized和鎖需要通過操作系統(tǒng)來仲裁誰獲得鎖夫偶,開銷比較高,而volatile開銷小很多觉增。因此在只需要保證可見性的條件下兵拢,使用volatile的性能要比使用鎖和synchronized高得多。
問:既然鎖和synchronized可以保證原子性逾礁,為什么還需要AtomicInteger這種的類來保證原子操作说铃?
答:鎖和synchronized需要通過操作系統(tǒng)來仲裁誰獲得鎖,開銷比較高嘹履,而AtomicInteger是通過CPU級的CAS操作來保證原子性截汪,開銷比較小。所以使用AtomicInteger的目的還是為了提高性能植捎。
問:還有沒有別的辦法保證線程安全
答:有衙解。盡可能避免引起非線程安全的條件——共享變量。如果能從設(shè)計(jì)上避免共享變量的使用焰枢,即可避免非線程安全的發(fā)生蚓峦,也就無須通過鎖或者synchronized以及volatile解決原子性、可見性和順序性的問題济锄。
問:synchronized即可修飾非靜態(tài)方式暑椰,也可修飾靜態(tài)方法,還可修飾代碼塊荐绝,有何區(qū)別
答:synchronized修飾非靜態(tài)同步方法時(shí)一汽,鎖住的是當(dāng)前實(shí)例;synchronized修飾靜態(tài)同步方法時(shí),鎖住的是該類的Class對象召夹;synchronized修飾靜態(tài)代碼塊時(shí)岩喷,鎖住的是synchronized關(guān)鍵字后面括號內(nèi)的對象。