本文地址:http://www.companysz.com/archimedes/p/java-study-note15.html,轉載請注明源地址。
線程的生命周期1、線程的生命周期
線程從產生到消亡的過程
一個線程在任何時刻都處于某種線程狀態(tài)(thread state)
線程生命周期狀態(tài)圖
誕生狀態(tài)
線程剛剛被創(chuàng)建
就緒狀態(tài)
線程的 start 方法已被執(zhí)行
線程已準備好運行
運行狀態(tài)
處理機分配給了線程,線程正在運行
阻塞狀態(tài)(Blocked)
在線程發(fā)出輸入/輸出請求且必須等待其返回
遇到用synchronized標記的方法而未獲得其監(jiān)視器暫時不能進入執(zhí)行時
休眠狀態(tài)(Sleeping)
執(zhí)行sleep方法而進入休眠
死亡狀態(tài)
線程已完成或退出
2、死鎖問題
死鎖
線程在運行過程中,其中某個步驟往往需要滿足一些條件才能繼續(xù)進行下去,如果這個條件不能滿足,線程將在這個步驟上出現阻塞
線程A可能會陷于對線程B的等待,而線程B同樣陷于對線程C的等待,依次類推,整個等待鏈最后又可能回到線程A。如此一來便陷入一個彼此等待的輪回中,任何線程都動彈不得,此即所謂死鎖(deadlock)
對于死鎖問題,關鍵不在于出現問題后調試,而是在于預防
設想一個游戲,規(guī)則為3個人站在三角形的三個頂點的位置上,三個邊上放著三個球,如圖所示。每個人都必須先拿到自己左手邊的球,才能再拿到右手邊的球,兩手都有球之后,才能夠把兩個球都放下
創(chuàng)建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(); //創(chuàng)建一個球類對象 Player0 p0 = new Player0(ball); //創(chuàng)建0號游戲者 Player1 p1 = new Player1(ball); //創(chuàng)建1號游戲者 Player2 p2 = new Player2(ball); //創(chuàng)建2號游戲者 p0.start(); //啟動0號游戲者 p1.start(); //啟動1號游戲者 p2.start(); //啟動2號游戲者 }}
運行結果:
若干次后將陷入死鎖,不再有輸出信息,即任何人都不能再同時擁有兩側的球
程序說明:
如果剛好3個人都拿到了左手邊的球,都等待那右手邊的球,則因為誰都不能放手,則這3個線程都將陷入無止盡的等待當中,這就構成了死鎖
為了便于觀察死鎖發(fā)生的條件,我們在每個游戲者放下兩邊的球后增加了sleep語句
為了避免死鎖,需要修改游戲規(guī)則,使每個人都只能先搶到兩側中號比較小的球,才能拿另一只球,這樣就不會再出現死鎖現象
3、控制線程的生命
結束線程的生命
用stop方法可以結束線程的生命
但如果一個線程正在操作共享數據段,操作過程沒有完成就用stop結束的話,將會導致數據的不完整,因此并不提倡使用此方法
通常,可通過控制run方法中循環(huán)條件的方式來結束一個線程
線程不斷顯示遞增整數,按下回車鍵則停止執(zhí)行:
class TestThread1 extends Thread { private boolean flag = true; public void stopme() { //在此方法中控制循環(huán)條件 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線程 }}
運行效果為按下回車鍵后則停止顯示
線程的優(yōu)先級線程調度
在單CPU的系統(tǒng)中,多個線程需要共享CPU,在任何時間點上實際只能有一個線程在運行
控制多個線程在同一個CPU上以某種順序運行稱為線程調度
Java虛擬機支持一種非常簡單的、確定的調度算法,叫做固定優(yōu)先級算法。這個算法基于線程的優(yōu)先級對其進行調度
線程的優(yōu)先級
每個Java線程都有一個優(yōu)先級,其范圍都在1和10之間。默認情況下,每個線程的優(yōu)先級都設置為5
在線程A運行過程中創(chuàng)建的新的線程對象B,初始狀態(tài)具有和線程A相同的優(yōu)先級
如果A是個后臺線程,則B也是個后臺線程
可在線程創(chuàng)建之后的任何時候,通過setPriority(int priority)方法改變其原來的優(yōu)先級
基于線程優(yōu)先級的線程調度
具有較高優(yōu)先級的線程比優(yōu)先級較低的線程優(yōu)先執(zhí)行
對具有相同優(yōu)先級的線程,Java的處理是隨機的
底層操作系統(tǒng)支持的優(yōu)先級可能要少于10個,這樣會造成一些混亂。因此,只能將優(yōu)先級作為一種很粗略的工具使用。最后的控制可以通過明智地使用yield()函數來完成
我們只能基于效率的考慮來使用線程優(yōu)先級,而不能依靠線程優(yōu)先級來保證算法的正確性
假設某線程正在運行,則只有出現以下情況之一,才會使其暫停運行
一個具有更高優(yōu)先級的線程變?yōu)榫途w狀態(tài)(Ready);
由于輸入/輸出(或其他一些原因)、調用sleep、wait、yield方法使其發(fā)生阻塞;
對于支持時間分片的系統(tǒng),時間片的時間期滿
例子:創(chuàng)建兩個具有不同優(yōu)先級的線程,都從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(); //放棄執(zhí)行權 } } }}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
結果說明:
具有較高優(yōu)先級的線程1一直運行到結束,具有較低優(yōu)先級的線程0才開始運行
雖然具有較高優(yōu)先級的線程1調用了yield方法放棄CPU資源,允許線程0進行爭奪,但馬上又被線程1搶奪了回去,所以有沒有yield方法都沒什么區(qū)別
如果在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(); //放棄執(zhí)行權 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
說明
具有較低優(yōu)先權的線程0在線程1沒有執(zhí)行完畢前也獲得了一部分執(zhí)行,但線程1還是優(yōu)先完成了執(zhí)行
Java虛擬機本身并不支持某個線程搶奪另一個正在執(zhí)行的具有同等優(yōu)先級線程的執(zhí)行權
通常,我們在一個線程內部插入yield()語句,這個方法會使正在運行的線程暫時放棄執(zhí)行,這是具有同樣優(yōu)先級的線程就有機會獲得調度開始運行,但較低優(yōu)先級的線程仍將被忽略不參加調度
您還可能感興趣:
java學習筆記系列:
java學習筆記14--多線程編程基礎1
java學習筆記13--反射機制與動態(tài)代理
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--開發(fā)環(huán)境平臺總結
新聞熱點
疑難解答