本文地址:http://www.49028c.com/archimedes/p/java-study-note15.html,轉載請注明源地址。
線程的生命周期1、線程的生命周期
線程從產生到消亡的過程
一個線程在任何時刻都處于某種線程狀態(thread state)
線程生命周期狀態圖
誕生狀態
線程剛剛被創建
就緒狀態
線程的 start 方法已被執行
線程已準備好運行
運行狀態
處理機分配給了線程,線程正在運行
阻塞狀態(Blocked)
在線程發出輸入/輸出請求且必須等待其返回
遇到用synchronized標記的方法而未獲得其監視器暫時不能進入執行時
休眠狀態(Sleeping)
執行sleep方法而進入休眠
死亡狀態
線程已完成或退出
2、死鎖問題
死鎖
線程在運行過程中,其中某個步驟往往需要滿足一些條件才能繼續進行下去,如果這個條件不能滿足,線程將在這個步驟上出現阻塞
線程A可能會陷于對線程B的等待,而線程B同樣陷于對線程C的等待,依次類推,整個等待鏈最后又可能回到線程A。如此一來便陷入一個彼此等待的輪回中,任何線程都動彈不得,此即所謂死鎖(deadlock)
對于死鎖問題,關鍵不在于出現問題后調試,而是在于預防
設想一個游戲,規則為3個人站在三角形的三個頂點的位置上,三個邊上放著三個球,如圖所示。每個人都必須先拿到自己左手邊的球,才能再拿到右手邊的球,兩手都有球之后,才能夠把兩個球都放下
創建3個線程模擬3個游戲者的行為。
class Balls { boolean flag0 = false; //0號球的標志變量,true表示已被人拿,false表示未被任何人拿 boolean flag1 = false; //1號球的標志變量 boolean flag2 = false; //2號球的標志變量}class Player0 extends Thread { //0號游戲者的類 PRivate Balls ball; public Player0(Balls b) { this.ball = b; } public void run() { while(true) { while(ball.flag1 == true){} //如果1號球已被拿走,則等待 ball.flag1 = true; //拿起1號球 while(ball.flag0 == true){} //如果0號球已被拿走,則等待 if(ball.flag1 == true && ball.flag0 == false) { ball.flag0 = true; //拿起0號球 System.out.println("Player0 has got two balls!"); ball.flag1 = false; //放下1號球 ball.flag0 = false; //放下0號球 try { sleep(1); //放下后休息1ms } catch (Exception e) { } } } }}class Player1 extends Thread { //1號游戲者的類 private Balls ball; public Player1(Balls b) { this.ball = b; } public void run() { while(true) { while(ball.flag0 == true){} ball.flag0 = true; while(ball.flag1 == true){} if(ball.flag0 == true && ball.flag1 == false) { ball.flag1 = true; System.out.println("Player0 has got two balls!"); ball.flag0 = false; ball.flag1 = false; try { sleep(1); } catch (Exception e) { } } } }}class Player2 extends Thread { //2號游戲者的類 private Balls ball; public Player2(Balls b) { this.ball = b; } public void run() { while(true) { while(ball.flag2 == true){} ball.flag2 = true; while(ball.flag1 == true){} if(ball.flag2 == true && ball.flag1 == false) { ball.flag1 = true; System.out.println("Player0 has got two balls!"); ball.flag2 = false; ball.flag1 = false; try { sleep(1); } catch (Exception e) { } } } }}public class playball { public static void main(String[] args) { Balls ball = new Balls(); //創建一個球類對象 Player0 p0 = new Player0(ball); //創建0號游戲者 Player1 p1 = new Player1(ball); //創建1號游戲者 Player2 p2 = new Player2(ball); //創建2號游戲者 p0.start(); //啟動0號游戲者 p1.start(); //啟動1號游戲者 p2.start(); //啟動2號游戲者 }}
運行結果:
若干次后將陷入死鎖,不再有輸出信息,即任何人都不能再同時擁有兩側的球
程序說明:
如果剛好3個人都拿到了左手邊的球,都等待那右手邊的球,則因為誰都不能放手,則這3個線程都將陷入無止盡的等待當中,這就構成了死鎖
為了便于觀察死鎖發生的條件,我們在每個游戲者放下兩邊的球后增加了sleep語句
為了避免死鎖,需要修改游戲規則,使每個人都只能先搶到兩側中號比較小的球,才能拿另一只球,這樣就不會再出現死鎖現象
3、控制線程的生命
結束線程的生命
用stop方法可以結束線程的生命
但如果一個線程正在操作共享數據段,操作過程沒有完成就用stop結束的話,將會導致數據的不完整,因此并不提倡使用此方法
通常,可通過控制run方法中循環條件的方式來結束一個線程
線程不斷顯示遞增整數,按下回車鍵則停止執行:
class TestThread1 extends Thread { private boolean flag = true; public void stopme() { //在此方法中控制循環條件 flag = false; } public void run() { int i = 0; while(flag) { System.out.println(i++); //如果flag為真則一直顯示遞增整數 } }}public class ext8_12 { public static void main(String[] args) throws IOException{ TestThread1 t = new TestThread1(); t.start(); new BufferedReader(new InputStreamReader(System.in)).readLine(); //等待鍵盤輸入 t.stopme(); //調用stopme方法結束t線程 }}
運行效果為按下回車鍵后則停止顯示
線程的優先級線程調度
在單CPU的系統中,多個線程需要共享CPU,在任何時間點上實際只能有一個線程在運行
控制多個線程在同一個CPU上以某種順序運行稱為線程調度
Java虛擬機支持一種非常簡單的、確定的調度算法,叫做固定優先級算法。這個算法基于線程的優先級對其進行調度
線程的優先級
每個Java線程都有一個優先級,其范圍都在1和10之間。默認情況下,每個線程的優先級都設置為5
在線程A運行過程中創建的新的線程對象B,初始狀態具有和線程A相同的優先級
如果A是個后臺線程,則B也是個后臺線程
可在線程創建之后的任何時候,通過setPriority(int priority)方法改變其原來的優先級
基于線程優先級的線程調度
具有較高優先級的線程比優先級較低的線程優先執行
對具有相同優先級的線程,Java的處理是隨機的
底層操作系統支持的優先級可能要少于10個,這樣會造成一些混亂。因此,只能將優先級作為一種很粗略的工具使用。最后的控制可以通過明智地使用yield()函數來完成
我們只能基于效率的考慮來使用線程優先級,而不能依靠線程優先級來保證算法的正確性
假設某線程正在運行,則只有出現以下情況之一,才會使其暫停運行
一個具有更高優先級的線程變為就緒狀態(Ready);
由于輸入/輸出(或其他一些原因)、調用sleep、wait、yield方法使其發生阻塞;
對于支持時間分片的系統,時間片的時間期滿
例子:創建兩個具有不同優先級的線程,都從1遞增到400000,每增加50000顯示一次class TestThread2 extends Thread { private int tick = 1; private int num; public TestThread2(int i) { this.num = i; } public void run() { while(tick < 400000) { tick++; if((tick % 50000) == 0) { //每隔50000進行顯示 System.out.println("Thread#" + num +",tick=" + tick); yield(); //放棄執行權 } } }}public class ext8_13 { public static void main(String[] args) { TestThread2[] runners = new TestThread2[2]; for(int i = 0; i < 2; i++) runners[i] = new TestThread2(i); runners[0].setPriority(2); runners[1].setPriority(3); for(int i = 0; i < 2; i++) runners[i].start(); }}
運行結果如下:
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #1, tick = 400000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
結果說明:
具有較高優先級的線程1一直運行到結束,具有較低優先級的線程0才開始運行
雖然具有較高優先級的線程1調用了yield方法放棄CPU資源,允許線程0進行爭奪,但馬上又被線程1搶奪了回去,所以有沒有yield方法都沒什么區別
如果在yield方法后增加一行sleep語句,讓線程1暫時放棄一下在CPU上的運行,哪怕是1毫秒,則線程0也可以有機會被調度。修改后的run方法如下:
public void run() { while(tick < 400000) { tick++; if((tick % 50000) == 0) { //每隔50000進行顯示 System.out.println("Thread#" + num +",tick=" + tick); yield(); //放棄執行權 try { sleep(1); } catch(Exception e) { } } } }
運行結果如下:
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 50000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #0, tick = 100000
Thread #1, tick = 350000
Thread #1, tick = 400000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
說明
具有較低優先權的線程0在線程1沒有執行完畢前也獲得了一部分執行,但線程1還是優先完成了執行
Java虛擬機本身并不支持某個線程搶奪另一個正在執行的具有同等優先級線程的執行權
通常,我們在一個線程內部插入yield()語句,這個方法會使正在運行的線程暫時放棄執行,這是具有同樣優先級的線程就有機會獲得調度開始運行,但較低優先級的線程仍將被忽略不參加調度
您還可能感興趣:
java學習筆記系列:
java學習筆記14--多線程編程基礎1
java學習筆記13--反射機制與動態代理
java學習筆記12--異常處理
java學習筆記11--集合總結
java學習筆記10--泛型總結
java學習筆記9--內部類總結
java學習筆記8--接口總結
java學習筆記7--抽象類與抽象方法
java學習筆記6--類的繼承、Object類
java學習筆記5--類的方法
java學習筆記4--對象的初始化與回收
java學習筆記3--類與對象的基礎
java學習筆記2--數據類型、數組
java學習筆記1--開發環境平臺總結
新聞熱點
疑難解答