為什么需要多線程?
打開任務(wù)管理器,我們會看到計算機里面有一個個的進程莹桅,進程進一步的劃分也就是線程了。我們運行java程序?qū)嶋H上就是運行了一個jvm進程烛亦,jvm默認(rèn)會用主線程來執(zhí)行main方法诈泼,另外jvm里面還有負(fù)責(zé)垃圾回收的其他工作線程等等,java語言做了多線程的支持煤禽,也就是說我們可以使用多線程實現(xiàn)多任務(wù)铐达。
現(xiàn)代計算機的cpu多采用多核架構(gòu),為了充分利用性能以及讓耗時操作(讀寫文件等等)更有效率檬果,所以在特定場合(高并發(fā))需要多線程操作瓮孙。
線程的生命周期
在Java程序中,一個線程對象只能調(diào)用一次start()方法啟動新線程汁汗,并在新線程中執(zhí)行run()方法衷畦。一旦run()方法執(zhí)行完畢,線程就結(jié)束了知牌。因此,Java線程的狀態(tài)有以下幾種:
New:新創(chuàng)建的線程斤程,尚未執(zhí)行角寸;
Runnable:運行中的線程,正在執(zhí)行run()方法的Java代碼忿墅;
Blocked:運行中的線程扁藕,因為某些操作被阻塞而掛起;
Waiting:運行中的線程疚脐,因為某些操作在等待中亿柑;
Timed Waiting:運行中的線程,因為執(zhí)行sleep()方法正在計時等待棍弄;
Terminated:線程已終止望薄,因為run()方法執(zhí)行完畢疟游。
線程安全問題
當(dāng)多個線程同時運行時荔睹,線程的調(diào)度由操作系統(tǒng)決定孔飒,程序本身無法決定胎食。因此喂江,任何一個線程都有可能在任何指令處被操作系統(tǒng)暫停哨鸭,然后在某個時間段后繼續(xù)執(zhí)行淮阐。
這個時候爆班,有個單線程模型下不存在的問題就來了:如果多個線程同時讀寫共享變量南吮,會出現(xiàn)數(shù)據(jù)不一致的問題花嘶。
多線程模型下笋籽,要保證邏輯正確,對共享變量進行讀寫時椭员,必須保證一組指令以原子方式執(zhí)行:即某一個線程執(zhí)行時车海,其他線程必須等待。通常我們會通過加鎖和解鎖的方式來確保執(zhí)行順訊拆撼。
但是加鎖的過程中如果出現(xiàn)使用不當(dāng)?shù)那闆r容劳,就會出現(xiàn)死鎖。
public void add(int m) {
synchronized(lockA) { // 獲得lockA的鎖
this.value += m;
synchronized(lockB) { // 獲得lockB的鎖
this.another += m;
} // 釋放lockB的鎖
} // 釋放lockA的鎖
}
public void dec(int m) {
synchronized(lockB) { // 獲得lockB的鎖
this.another -= m;
synchronized(lockA) { // 獲得lockA的鎖
this.value -= m;
} // 釋放lockA的鎖
} // 釋放lockB的鎖
}
如果兩個線程去執(zhí)行上面兩個方法就會出現(xiàn)死鎖闸度。那么我們應(yīng)該如何避免死鎖呢竭贩?答案是:線程獲取鎖的順序要一致。
線程池
Java語言雖然內(nèi)置了多線程支持莺禁,啟動一個新線程非常方便留量,但是,創(chuàng)建線程需要操作系統(tǒng)資源(線程資源哟冬,椔ハǎ空間等),頻繁創(chuàng)建和銷毀大量線程需要消耗大量時間浩峡。
那么我們就可以把很多小任務(wù)讓一組線程來執(zhí)行可岂,而不是一個任務(wù)對應(yīng)一個新線程。這種能接收大量小任務(wù)并進行分發(fā)處理的就是線程池翰灾。
簡單地說缕粹,線程池內(nèi)部維護了若干個線程,沒有任務(wù)的時候纸淮,這些線程都處于等待狀態(tài)平斩。如果有新任務(wù),就分配一個空閑線程執(zhí)行咽块。如果所有線程都處于忙碌狀態(tài)绘面,新任務(wù)要么放入隊列等待,要么增加一個新線程進行處理。
threadLocal
顧名思義揭璃,指的就是線程里面的儲存空間晚凿,在當(dāng)前線程能一直獲取同一個實例。實際上塘辅,可以把ThreadLocal看成一個全局Map<Thread, Object>:每個線程獲取ThreadLocal變量時晃虫,總是使用Thread自身作為key。