1 生命週期
線程被創建和啓動後(調用start()方法後),並不是已啓動就進入執行狀態,也不是一直處於執行狀態。線程狀態轉換圖如下:
1.1 新建和就緒狀態
新建狀態:當程序new了一個線程對象之後,該線程就處於新建狀態。和其他java對象一樣,僅僅有JVM爲其分配內存,並初始化成員變量的值。
就緒狀態:線程對象調用了start()方法後。JVM爲其創建方法調用棧和程序計數器。此時,線程並沒有開始運行,何時運行則取決於JVM裏線程調度器的調度。
注意:啓動線程是start()方法,不是run()方法。直接調用run(),系統會把線程對象當成一個普通對象,而run()方法也是一個普通方法,而不是線程執行體。因此,在run()方法返回之前,其他線程無法併發執行。
如下測試代碼:
package thread;
/**
* Created by Zen9 on 2016/3/4.
*/
public class Runtest extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + " " +i);
}
}
public static void main(String[] args) {
//創建兩個新線程,並調用run()方法
new Runtest().run();
new Runtest().run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
程序則順序地輸出結果,變成了單線程。
1.2 運行和阻塞狀態
運行狀態:處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體。
如果計算機只有一個CPU,那麼在任何時刻只有一個線程處於運行狀態。當然,在一個多處理器的機器上,將會有多個線程並行執行;當線程數大於處理器數時,依然會存在多個線程在同一個CPU上輪換的現象。
線程進入阻塞狀態的情況:
- 調用sleep()方法主動放棄佔用的處理器資源
- 調用了一個阻塞式IO方法,在方法返回之前,線程被阻塞
- 線程試圖獲取一個同步監視器,但該同步監視器正被其他線程所持有
- 線程在等待某個通知(notify)
- 調用了suspend()方法將線程掛起。(易導致死鎖)
阻塞的線程重新進入就緒狀態:
- 調用的sleep()方法,過了指定時間
- 調用的阻塞IO方法已經返回
- 線程成功獲得了同步監視器
- 線程在等待通知時,其他線程發出了一個通知
- 掛起狀態的線程被調用了resume()恢復方法
1.3 線程終止
線程結束後,就處於終止狀態:
- run()或call()方法執行完成,線程正常結束。
- 線程拋出一個未捕獲的Exception或Error
- 直接調用stop()方法結束線程。(易導致死鎖)
2 控制線程
2.1 join線程
join():讓一個線程等待另一個線程完成。當某個程序執行流中調用了其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完爲止。
package thread;
/**
* Created by Zen9 on 2016/3/4.
*/
public class JoinThreadTest extends Thread{
//有參數的構造函數,設置線程名字
public JoinThreadTest(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
public static void main(String[] args) throws Exception{
new JoinThreadTest("新線程").start();
for (int i = 0; i < 100; i++) {
if (i == 20){
JoinThreadTest joinThreadTest = new JoinThreadTest("被Join的線程");
joinThreadTest.start();
//調用joinThreadTest線程的join()方法,main線程則會被阻塞
joinThreadTest.join();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
運行結果:
join()方法重載形式:
- join():等待被join的線程執行完成
- join(long millis):等待被join的線程的時間最長爲millis毫秒。
2.2 後臺線程
有一種線程,它是後臺運行的,它的任務是爲其他的線程提供服務,這種線程被稱爲“後臺線程(Daemon Thread)”,又稱爲“守護線程”或“精靈線程”。
特徵:當所有的前臺線程都死亡,後臺線程也會自動死亡。
例子:JVM的垃圾回收線程。
調用Thread對象的setDaemon(true)方法可將指定線程設置成後臺線程。isDaemon()方法,判斷是否後臺線程。
前臺線程創建的子線程默認是前臺線程,後臺…..默認是….後臺線程。
package thread;
/**
* Created by Zen9 on 2016/3/4.
*/
public class DaemonThreadTest extends Thread{
public DaemonThreadTest(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName()+":"+i);
}
}
public static void main(String[] args) {
DaemonThreadTest daemonThreadTest = new DaemonThreadTest("後臺線程");
//設置爲後臺線程
daemonThreadTest.setDaemon(true);
daemonThreadTest.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
2.3 線程睡眠:sleep
如果需要讓當前正在執行的線程暫停一段時間,並進入阻塞狀態,則可以通過調用Thread類的靜態sleep()方法來實現。
package thread;
import java.util.Date;
/**
* Created by Zen9 on 2016/3/4.
*/
public class SleepMethodTest {
public static void main(String[] args) throws Exception{
for (int i = 0; i < 10; i++) {
System.out.println("當前時間:"+new Date());
if (i==7){
//參數爲毫秒
Thread.sleep(10000);
}
}
}
}
2.4 線程讓步:yield
yield():讓當前正在執行的線程暫停,但不會阻塞該線程。只是將該線程轉入就緒狀態。yield()只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次。
當某個線程調用了yield()方法暫停之後,只有優先級與當前線程相同或者優先級比較當前線程更高的處於就緒狀態的線程纔會獲得執行的機會。
2.5 改變線程優先級
每個線程執行時都具有一定的優先級,優先級高的線程獲得較多的執行機會,而優先級低的線程則獲得較少的執行機會。
每個線程默認的優先級都與創建它的父線程的優先級相同。
Thread類提供的方法:
- setPriority(int newPriority):設置線程優先級,參數是一個整數,範圍1~10,或使用三個靜態常量:MAX_PRIORITY(10)、MIN_PRIORITY(1)、NORM_PRIORITY(5)
- getPriority():獲取線程優先級
推薦使用三個靜態常量來設置線程優先級,這樣纔可以程序具有最好的移植性。因爲不同的操作系統優先級不相同(有的爲7個,有的爲10個)。