并發(fā)基礎
線程
表示一條單獨的執(zhí)行流玛臂,有自己自己單獨的程序計數(shù)器和棧;
1.1 創(chuàng)建方法
- 繼承Thread類
- 實現(xiàn)Runnable接口
如果不是調用Thread.start開啟線程讽营,而是直接調用其run方法泡徙,那就不會有開啟一個新線程的作用,這種情況下堪藐,run方法只是作為一個普通方法被調用的礁竞;
1.2 基本屬性
- id和name
id是一個遞增的整數(shù),每創(chuàng)建一個線程就會加一寂嘉; - 優(yōu)先級
Java中1-10,默認為5硼端;
這里需要注意寓搬,設置優(yōu)先級對于操作 系統(tǒng)而言只是一個建議,編程時不要過分依賴優(yōu)先級 -
狀態(tài)
可以用Thread的getState()方法得到線程的狀態(tài)镣典,得到的值是一個枚舉類型唾琼,如下:
NEW:沒有調用start的線程
RUNNABLE:調用start后,正在執(zhí)行run方法并且沒有阻塞的狀態(tài)赶舆;注意:線程在運行或者具備運行條件祭饭,只是在等待操作系統(tǒng)調度
BLOCKED:線程在等待鎖倡蝙,視圖進入同步塊
WAITING: 在等待某個條件
TIMED_WAITING: 在等待超時
TERMINATED: 運行結束后的狀態(tài)
1.3 基本方法
- isAlive()
啟動后,run方法運行結束前猪钮,返回都是true - isDaemon()
先看下什么是守護線程析既,對于一般的線程谆奥,程序在所有線程都結束后,才會退出宰译,但是對于守護線程魄懂,當整個程序剩下的都是daemon線程時市栗,就會退出咳短;
daemon線程一般是其他線程的輔助線程 - sleep()
讓線程睡眠指定時間蛛淋,睡眠期間,該線程會讓出CPU勾效;
注意:這里傳入的時間不一定會精準叛甫; - yield()
該方法會建議調度器其监,目前當前線程不著急執(zhí)行,可以先讓其他線程運行 - join()
可以讓調用join的線程(例如主線程)等待該線程(執(zhí)行計算的子線程)執(zhí)行結束
1.4 多線程可能存在的問題
- 競態(tài)條件:指執(zhí)行結果不確定哮奇,和執(zhí)行時序有關
可通過synchroniezd關鍵字睛约、使用顯示鎖、使用原子變量解決 - 內存可見性
造成這種問題的原因是贸伐,數(shù)據(jù)會被存儲在各種高速緩存中怔揩,當訪問/修改一個變量時商膊,不一定會直接從內存中讀取/寫入,這就可能導致一個線程對值的修改藐翎,另一個線程無法及時更新到实幕;
可以通過volatile、synchronized關鍵字或者顯示鎖方式解決
1.5 優(yōu)缺點
- 優(yōu)點:充分利用CPU和硬件資源末贾,保證GUI及時刷新等
- 缺點:
創(chuàng)建線程需要耗費系統(tǒng)資源整吆,為線程創(chuàng)建程序計數(shù)器,棧等都是需要開銷的拴测;
線程的切換也是有成本的昼扛,主要是上下文切換帶來的成本, 當切換時渺鹦,需要保存當前線程的上下文狀態(tài)(包括程序計數(shù)器的值蛹含,CPU寄存器的值等)到內存中
synchronized的理解
2.1 用法
可用于修飾類的:
- 靜態(tài)方法
保護的是當前的類對象 - 實例方法
保護的是這個實例,這里需要注意:多個線程是可以同時執(zhí)行同一個synchronized修飾的實例方法的吸耿,只要它們針對的是不同的對象即可酷窥。
因此蓬推,需要明確一點:
synchronized修飾實例方法,保護的是當前的實例對象糕珊,即this毅糟;每一個對象都有一個鎖和等待隊列姆另,同一時間,鎖只能被一個線程所持有蜕青;具體執(zhí)行synchronized實例方法的過程如下:
1) 嘗試獲得鎖右核,若得到渺绒,則執(zhí)行菱鸥,否則氮采,加入等待隊列染苛,阻塞并等待喚醒
2) 執(zhí)行方法
3) 釋放鎖,如果等待隊列有線程躯概,則取一個并將其喚醒畔师;注意看锉,如果有多個等待線程,則喚醒哪一個是不一定的呻此,不保證公平性
- 代碼塊
任意對象都有一個鎖和等待隊列腔寡,也就是說任何對象都可以作為鎖對象
注意: synchronized關鍵字保護的是對象而不是具體的代碼蹬蚁,理解這一點是很重要的。只要訪問的是同一個對象的synchronized方法贝乎,即使是不同的代碼叽粹,也會被保證同步順序方法。
并且锤灿,只能保證加了synchronized修飾的方法同步執(zhí)行辆脸,synchronized方法無法保證非synchronized方法被同時執(zhí)行啡氢;因此术裸,在保護變量時亭枷,需要在所有訪問該變量的方法上加synchronized修飾叨粘。
2.2 特點
- 可重入性:當其獲得了鎖后,當進入需要同樣鎖的代碼時答倡,可以直接進入冻晤,而無需再等待
- 提供內存可見性:如果只是為了獲得可見性的話鼻弧,優(yōu)先考慮更加輕量的volatile關鍵字
- 可能產生死鎖
當使用synchronized時,需要特別注意修飾的對象是否是同一個叉存,即是否使用了相同的鎖度帮;
線程間的協(xié)作
3.1 wait/notify
除了用于鎖的等待隊列笨篷, 線程還有另一個等待隊列, 表示條件隊列练俐,用于線程間的協(xié)作冕臭。
當調用了wait之后辜贵,就會把當前線程加入條件隊列并阻塞, 表示當前線程執(zhí)行不下去了鼻由,需要等待一個條件,這個條件自己改變不了跺撼,需要其他線程改變讨彼,當其他線程改變了條件后柿祈,應該調用notify方法躏嚎。
wait/notify方法只能在synchronized代碼塊內被調用,否則會拋異常重荠。
wait的具體過程
- 把當前的線程加入條件隊列戈鲁,釋放對象鎖嘹叫,阻塞等待,線程狀態(tài)變?yōu)閃AITING或者TIME_WAITING
- 等待時間到或者被其他線程調用notify/notifyAll從條件隊列中移除婆芦,這時消约,需要重新競爭對象鎖:
a) 可以獲得员帮,線程狀態(tài)變?yōu)镽UNNABLE集侯,從wait調用中返回
b) 無法獲得,該線程會加入對象鎖等待隊列浓体,線程狀態(tài)變?yōu)锽LOCKED辈讶,獲得鎖后才會從wait調用中返回;
從wait調用中返回后生闲,不代表其等待條件就一定成立碍讯,需要重新檢查等待條件:
synchronized (obj) {
while(條件不成立) {
obj.wait();
}
// do sth
}
調用notify后,并不會釋放對象鎖蝎困,只有在包含notify的synchronized代碼塊執(zhí)行結束后倍啥,等待的線程才會從wait調用中返回
總結:wait/notify被不同的線程調用虽缕,但是二者共享相同的鎖和條件等待隊列(即相同鎖對象的synchronized代碼塊內),二者圍繞一個共享的條件變量進行協(xié)作伍派,這個變量是程序自己維護的拙已,當不滿足時摧冀,wait并進入條件等待隊列,另一個線程修改了該條件變量并調用了notify索昂,然后調用wait的線程被喚醒建车,該線程需要重新檢查條件變量。在使用wait/notify時椒惨,需要明確協(xié)作的共享變量和條件是什么缤至。
3.2 生產者/消費者模式
Java提供的阻塞隊列有:
- BlockingQueue
- ArrayBlockingQueue
- LinkedBlockingQueue等
3.3 同時開始
其他線程都先wait,條件滿足后notifyAll即可
3.4 等待結束
以未就緒線程數(shù)量為條件康谆,一個線程就緒后领斥,將條件-1,當條件為0時沃暗,notifyAll即可
Java提供了CountDownLatch用于這種情況
3.5 異步結果
Java提供的主要涉及到的是:
- 表示異步結果的接口Future和其實現(xiàn)FutureTask
- 用于執(zhí)行異步任務的接口Executor月洛,和具有更多功能的子接口ExecutorService
- 創(chuàng)建上面兩種Executor的工廠類Executors
3.5 集合點
當所有線程都執(zhí)行結束后,到達集合點嚼黔,交換數(shù)據(jù)并進行下一步動作细层;這種和等待結束是類似的;
Java提供了CyclicBarrier
線程的中斷
4.1 中斷
主要用到的機制是中斷唬涧,下面來看下中斷疫赎。
中斷并不是強迫終止一個線程,它是一種協(xié)作機制碎节,是傳遞給線程一個取消信號捧搞,但何時退出是由線程來決定的。
Java主要提供了下面幾個方法:
- isInterrupted():返回當前線程的中斷標志位是否為true
- interrupt():中斷對應的線程
- static interrupted():返回當前線程的中斷標志位是否為true钓株,并清空中斷標志位為false
4.2 線程對中斷的反應
根據(jù)線程當前的狀態(tài):調用interrupt()后的變化如下:
- RUNNABLE:只是設置中斷標志位实牡,線程應該自己檢查該標志位的狀態(tài)陌僵,例如轴合,如果它是true那就應該退出循環(huán)
- WAITING/TIMED_WAITING:會清空中斷標志位,并拋出InterruptedException碗短,該異常是受檢查異常受葛,必須處理:
1) 向上傳遞
2) 無法傳遞時(例如在run方法中),則需要進行合適的清理工作偎谁,并調用interrupt方法設置中斷標志位总滩,讓其他代碼知道其發(fā)生了中斷 - BLOCKED:只是設置標志位,線程狀態(tài)不會變化巡雨。
這里需要注意闰渔,使用synchronized關鍵字獲取鎖的過程中,不會相應中斷請求铐望,這時synchronized的局限性冈涧,如果這對程序是個問題,那就應該使用顯示鎖 - NEW/TERMINATE:無效正蛙,標志位也不會變化
4.3 如何取消/關閉線程
如果不清楚線程在做什么督弓,不要貿然使用interrupt方法;
具體的取消方法可以參考原生實現(xiàn):Future接口的cancle()乒验、ExecutorService的shutdown()愚隧,shutdownNow()等