多線程
并發與并行、程序,線程排程自行百度
線程(thread):是一個程序中的其中一條執行路徑,CPU排程的最基本排程的機關。同一個程序中線程可以共享一些記憶體(堆、方法區),每一個線程又有自己的獨立空間(棧、程式計數器)。因為線程之間有共享的記憶體,在實作資料共享方面,比較友善,但是又因為共享資料的問題,會有線程安全問題。
當運作Java程式時,其實已經有一個線程了,那就是main線程。
Thread類
所有的線程對象都必須是Thread類或其子類的執行個體,Java中通過繼承Thread類來建立并啟動多線程的步驟如下:
- 定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,是以把run()方法稱為線程執行體。
- 建立Thread子類的執行個體,即建立了線程對象
- 調用線程對象的start()方法來啟動該線程
Runnable接口
我們還可以實作Runnable接口,重寫run()方法,然後再通過Thread類的對象代理啟動和執行我們的線程體run()方法。步驟如下:
- 定義Runnable接口的實作類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
- 建立Runnable實作類的執行個體,并以此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正 的線程對象。
- 調用線程對象的start()方法來啟動線程。
例: public class MyRunnable implements Runnable //定義實作線程類
MyRunnable mr = new MyRunnable(); //建立線程對象
Thread t = new Thread(mr); //通過Thread類的執行個體,啟動線程
t.start();
實際上所有的多線程代碼都是通過運作Thread的start()方法來運作的。是以,不管是繼承Thread類還是實作 Runnable接口來實作多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程程式設計的基礎。
tips:Runnable對象僅僅作為Thread對象的target,Runnable實作類裡包含的run()方法僅作為線程執行體。 而實際的線程對象依然是Thread執行個體,隻是該Thread線程負責執行其target的run()方法。
兩種方式的差別
1、繼承的方式有單繼承的限制,實作的方式可以多實作
2、啟動方式不同
3、繼承:在實作共享資料時,可能需要靜态的
實作:隻要共享同一個Runnable實作類的對象即可。
4、繼承:選擇鎖時this可能不能用,
實作:選擇鎖時this可以用。
匿名内部類對象來實作線程的建立和啟動
new Thread("新的線程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執行!"+i);
}
}
}.start();
構造方法
public Thread() :配置設定一個新的線程對象。
public Thread(String name) :配置設定一個指定名字的新的線程對象。
public Thread(Runnable target) :配置設定一個帶有指定目标新的線程對象。
public Thread(Runnable target,String name) :配置設定一個帶有指定目标新的線程對象并指定名字。
線程常用方法
volatile:修飾變量
變量不一定在什麼時候值就會被修改了,為了總是得到最新的值,volatile修飾之後那麼每次都從主存中去取值,不會在寄存器中緩存它的值。
守護線程
守護線程有個特點,就是如果所有非守護線程都死亡,那麼守護線程自動死亡。
調用setDaemon(true)方法可将指定線程設定為守護線程。必須線上程啟動之前設定,否則會報IllegalThreadStateException異常。
調用isDaemon()可以判斷線程是否是守護線程。
線程安全
線程安全問題的判斷
1、是否有多個線程
2、這多個線程是否使用共享資料
3、這些線程在使用共享資料時,是否有寫有讀操作
同步代碼塊
synchronized 關鍵字可以用于方法中的某個區塊中,表示隻對這個區塊的資源實行互斥通路。 格式:
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖必須是對象
鎖對象可以是任意類型。
多個線程對象必須使用同一把鎖。 注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程隻能在外等着(BLOCKED)。
同步方法
使用synchronized修飾的方法,就叫做同步方法,保證持有鎖線程執行該方法的時候,其他線程隻能在方法外等着
【其他修飾符】 synchronized 傳回值類型 方法名(【形參清單】)【throws 異常清單】{
//可能會産生線程安全問題的代碼
鎖對象不能由我們自己選,它是預設的:
(1)靜态方法:鎖對象是目前類的Class對象
(2)非靜态方式:this
線程間通信
當“資料緩沖區”滿的時候,“生産者”需要wait,等着被喚醒;
當“資料緩沖區”空的時候,“消費者”需要wait,等着被喚醒。
在一個線程滿足某個條件時,就進入等待狀态(wait()/wait(time)), 等待其他線程執行完他們的指定代碼過後再将其喚醒(notify());或可以指定wait的時間,等時間到了自動喚醒;在有多個線程進行等待時,如果需要,可以使用 notifyAll()來喚醒所有的等待線程。
- wait:線程不再活動,不再參與排程,進入 wait set 中,是以不會浪費 CPU 資源,也不會去競争鎖了,這時的線程狀态即是 WAITING或TIMED_WAITING。它還要等着别的線程執行一個特别的動作,也即是“通知(notify)”或者等待時間到,在這個對象上等待的線程從wait set 中釋放出來,重新進入到排程隊列(ready queue)中
- notify:則選取所通知對象的 wait set 中的一個線程釋放;
- notifyAll:則釋放所通知對象的 wait set 上的全部線程。
注意:
被通知線程被喚醒後也不一定能立即恢複執行,因為它當國中斷的地方是在同步塊内,而此刻它已經不持有鎖,是以她需要再次嘗試去擷取鎖(很可能面臨其它線程的競争),成功後才能在當初調用 wait 方法之後的地方恢複執行。
總結如下:
- 如果能擷取鎖,線程就從 WAITING 狀态變成 RUNNABLE(可運作) 狀态;
- 否則,線程就從 WAITING 狀态又變成 BLOCKED(等待鎖) 狀态
調用wait和notify方法需要注意的細節
- wait方法與notify方法必須要由同一個鎖對象調用。因為:對應的鎖對象可以通過notify喚醒使用同一個鎖對象調用的wait方法後的線程。
- wait方法與notify方法是屬于Object類的方法的。因為:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了Object類的。
- wait方法與notify方法必須要在同步代碼塊或者是同步函數中使用。因為:必須要通過鎖對象調用這2個方法。
等待喚醒機制可以解決經典的“生産者與消費者”的問題
要解決該問題,就必須讓生産者線程在緩沖區滿時等待(wait),暫停進入阻塞狀态,等到下次消費者消耗了緩沖區中的資料的時候,通知(notify)正在等待的線程恢複到就緒狀态,重新開始往緩沖區添加資料。反之亦然
線程生命周期
一、站線上程的角度上:5種

1、建立:建立了線程對象,還未start
2、就緒:已啟動,并且可被CPU排程
3、運作:正在被排程
4、阻塞:遇到了:sleep(),wait(),wait(time),其它線程的join(),join(time),suspend(),鎖被其他線程占用等
解除阻塞回到就緒狀态:sleep()時間,notify(),wait的時間到,加塞的線程結束,加塞的時間到,resume(),其他占用鎖的線程釋放了鎖等。
5、死亡:run()正常結束,遇到了未處理的異常或錯誤,stop()
程式隻能對建立狀态的線程調用start(),并且隻能調用一次,如果對非建立狀态的線程,如已啟動的線程或已死亡的線程調用start()都會報錯IllegalThreadStateException異常。
二、站在代碼的角度上6種
在java.lang.Thread.State的枚舉類中這樣定義
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
1、建立NEW:建立了線程對象,還未start
2、可運作RUNNABLE:可以被CPU排程,或者正在被排程
3、阻塞BLOCKED:等待鎖
4、等待WAITING:wait(),join()等沒有設定時間的,必須等notify(),或加塞的線程結束才能恢複
5、有時間等待TIMED_WAITING:sleep(time),wait(time),join(time)等有時間的阻塞,等時間到了恢複,或被interrupt也會恢複
6、終止TERMINATED:run()正常結束,遇到了未處理的異常或錯誤,stop()
釋放鎖操作與死鎖
任何線程進入同步代碼塊、同步方法之前,必須先獲得對同步螢幕的鎖定,那麼何時會釋放對同步螢幕的鎖定呢?
1、釋放鎖的操作
目前線程的同步方法、同步代碼塊執行結束。
目前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導緻目前線程異常結束。
目前線程在同步代碼塊、同步方法中執行了鎖對象的wait()方法,目前線程被挂起,并釋放鎖。
2、不會釋放鎖的操作
線程執行同步代碼塊或同步方法時,程式調用Thread.sleep()、Thread.yield()方法暫停目前線程的執行。
線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法将該線程挂起,該線程不會釋放鎖(同步螢幕)。應盡量避免使用suspend()和resume()這樣的過時來控制線程。
3、死鎖
不同的線程分别鎖住對方需要的同步螢幕對象不釋放,都在等待對方先放棄時就形成了線程的死鎖。一旦出現死鎖,整個程式既不會發生異常,也不會給出任何提示,隻是所有線程處于阻塞狀态,無法繼續。