多線程
當運行Java程序時,其實已經有一個線程了,那就是main線程。
所有的線程對象都必須是Thread類或其子類的實例,
-
定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱爲線程執行體。
-
創建Thread子類的實例,即創建了線程對象
-
調用線程對象的start()方法來啓動該線程
步驟如下:
-
定義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類還是實現
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();
守護線程有個特點,就是如果所有非守護線程都死亡,那麼守護線程自動死亡。
調用setDaemon(true)方法可將指定線程設置爲守護線程。必須在線程啓動之前設置,否則會報IllegalThreadStateException異常。
線程安全問題的判斷
1、是否有多個線程
3、這些線程在使用共享數據時,是否有寫有讀操作
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖必須是對象
多個線程對象必須使用同一把鎖。 注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等着(BLOCKED)。
【其他修飾符】 synchronized 返回值類型 方法名(【形參列表】)【throws 異常列表】{
//可能會產生線程安全問題的代碼
}
鎖對象不能由我們自己選,它是默認的:
(1)靜態方法:鎖對象是當前類的Class對象
(2)非靜態方式:this
當“數據緩衝區”滿的時候,“生產者”需要wait,等着被喚醒;
當“數據緩衝區”空的時候,“消費者”需要wait,等着被喚醒。
-
-
notify:則選取所通知對象的 wait set 中的一個線程釋放;
-
notifyAll:則釋放所通知對象的 wait set 上的全部線程。
被通知線程被喚醒後也不一定能立即恢復執行,因爲它當初中斷的地方是在同步塊內,而此刻它已經不持有鎖,所以她需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),成功後才能在當初調用 wait 方法之後的地方恢復執行。
總結如下:
-
如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE(可運行) 狀態;
-
否則,線程就從 WAITING 狀態又變成 BLOCKED(等待鎖) 狀態
調用wait和notify方法需要注意的細節
-
wait方法與notify方法必須要由同一個鎖對象調用。因爲:對應的鎖對象可以通過notify喚醒使用同一個鎖對象調用的wait方法後的線程。
-
wait方法與notify方法是屬於Object類的方法的。因爲:鎖對象可以是任意對象,而任意對象的所屬類都是繼承了Object類的。
-
等待喚醒機制可以解決經典的“生產者與消費者”的問題
要解決該問題,就必須讓生產者線程在緩衝區滿時等待(wait),暫停進入阻塞狀態,等到下次消費者消耗了緩衝區中的數據的時候,通知(notify)正在等待的線程恢復到就緒狀態,重新開始往緩衝區添加數據。反之亦然
一、站在線程的角度上:5種
1、新建:創建了線程對象,還未start
2、就緒:已啓動,並且可被CPU調度
3、運行:正在被調度
4、阻塞:遇到了:sleep(),wait(),wait(time),其它線程的join(),join(time),suspend(),鎖被其他線程佔用等
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也會恢復
任何線程進入同步代碼塊、同步方法之前,必須先獲得對同步監視器的鎖定,那麼何時會釋放對同步監視器的鎖定呢?
1、釋放鎖的操作
當前線程的同步方法、同步代碼塊執行結束。
當前線程在同步代碼塊、同步方法中出現了未處理的Error或Exception,導致當前線程異常結束。
2、不會釋放鎖的操作
線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法暫停當前線程的執行。
線程執行同步代碼塊時,其他線程調用了該線程的suspend()方法將該線程掛起,該線程不會釋放鎖(同步監視器)。應儘量避免使用suspend()和resume()這樣的過時來控制線程。
3、死鎖
不同的線程分別鎖住對方需要的同步監視器對象不釋放,都在等待對方先放棄時就形成了線程的死鎖。一旦出現死鎖,整個程序既不會發生異常,也不會給出任何提示,只是所有線程處於阻塞狀態,無法繼續。