控制線程
摘要:
Java的線程支持提供了一些便捷的工具方法,通過這些便捷的工具方法可以很好地控制線程的執行
1. join線程控制,讓一個線程等待另一個線程完成的方法
2. 后台線程,又稱為守護線程或精靈線程。它的任務是為其他的線程提供服務,如果所有的前台線程都死亡,后台線程會自動死亡
3. 線程睡眠sleep,讓當前正在執行的線程暫停一段時,並進入阻塞狀態
4. 線程讓步yield,讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態
一、join線程
Thread提供了讓一個線程等待另一個線程完成的方法join()方法。當在某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完為止。join()方法通常由使用線程的程序調用,以將大問題划分成許多小問題,每個小問題分配一個線程。當所有的小問題都得到處理后,再調用主線程來進一步操作。
-
package test;
-
-
public class JoinThread extends Thread {
-
// 提供一個有參數的構造器,用於設置該線程的名字
-
public JoinThread(String name) {
-
super(name);
-
}
-
-
// 重寫 run方法,定義線程執行體
-
public void run() {
-
for (int i = 0; i < 100; i++) {
-
System.out.println(getName() + "" + i);
-
}
-
}
-
-
public static void main(String[] args) throws Exception {
-
// 啟動子線程
-
new JoinThread(" 新線程 ").start();
-
for (int i = 0; i < 100; i++) {
-
if (i == 20) {
-
JoinThread jt = new JoinThread(" 被Join的線程 ");
-
jt.start();
-
// main 線程調用了 jt線程的join()方法,main線程
-
// 必須等 jt執行結束才會向下執行
-
jt.join();
-
}
-
System.out.println(Thread.currentThread().getName() + "" + i);
-
}
-
}
-
}
運行結果:
main 0 main 1 main 2 main 3 新線程 0 main 4 新線程 1 main 5 新線程 2 main 6 新線程 3 |
main 7 新線程 4 main 8 新線程 5 main 9 新線程 6 新線程 7 新線程 8 main 10 新線程 9 main 11 |
新線程 10 main 12 新線程 11 main 13 新線程 12 main 14 新線程 13 main 15 新線程 14 main 16 ………… |
main 18 新線程 17 main 19 新線程 18 新線程 19 ………… 新線程 99 被Join的線程 0 ………… 被Join的線程 99 main 20 |
上面程序中一共有3個線程,主方法開始時就啟動了名為"新線程"的子線程,該子線程將會和main線程並發執行。當主線程的循環變量i等於20時啟動了名為"被Join的線程"的線程,該線程不會和main線程並發執行。main線程必須等該線程執行結束后才可以向下執行。在名為"被Join的線程"的線程執行時,實際上只有2個子線程並發執行,而主線程處於等待狀態。運行上面程序。從上面的運行結果可以看出,主線程執行到i=20時啟動,並join了名為"被Join的線程"的線程,所以主線程將一直處於阻塞狀態,直到名為"被Join的線程"的線程執行完成。
二、后台線程
有一種線程,它是在后台運行的,它的任務是為其他的線程提供服務,這種線程被稱為后台線程(Daemon Thread),又稱為守護線程或精靈線程。JVM的垃圾回收線程就是典型的后台線程。后台線程有個特征:如果所有的前台線程都死亡,后台線程會自動死亡。
調用Thread對象的setDaemon(true)方法可將指定線程設置成后台線程。下面程序將執行線程設置成后台線程,可以看到當所有的前台線程死亡時,后台線程隨之死亡。當整個虛擬機中只剩下后台線程時,程序就沒有繼續運行的必要了,所以虛擬機也就退出了。
-
public class DaemonThread extends Thread {
-
// 定義后台線程的線程執行體與普通線程沒有任何區別
-
public void run() {
-
for (int i = 0; i < 1000; i++) {
-
System.out.println(getName() + "" + i);
-
}
-
}
-
-
public static void main(String[] args) {
-
DaemonThread t = new DaemonThread();
-
// 將此線程設置成后台線程
-
t.setDaemon(true);
-
// 啟動后台線程
-
t.start();
-
for (int i = 0; i < 10; i++) {
-
System.out.println(Thread.currentThread().getName() + "" + i);
-
}
-
// ----- 程序執行到此處,前台線程( main線程)結束------
-
// 后台線程也應該隨之結束
-
}
-
}
運行結果:
main 0 Thread-0 0 main 1 Thread-0 1 main 2 Thread-0 2 main 3 Thread-0 3 main 4 Thread-0 4 main 5 |
Thread-0 5 main 6 Thread-0 6 main 7 Thread-0 7 main 8 Thread-0 8 main 9 Thread-0 9 Thread-0 10 Thread-0 11 |
Thread-0 12 Thread-0 13 Thread-0 14 Thread-0 15 Thread-0 16 Thread-0 17 Thread-0 18 Thread-0 19 |
上面程序中的t線程設置成后台線程,然后啟動該線程,本來該線程應該執行到i等於999時才會結束,但運行程序時不難發現該后台線程無法運行到999,因為當主線程也就是程序中唯一的前台線程運行結束后,JVM會主動退出,因而后台線程也就被結束了。Thread類還提供了一個isDaemon0方法,用於判斷指定線程是否為后台線程
從上面程序可以看出,主線程默認是前台線程, t線程默認也是前台線程。並不是所有的線程默認都是前台線程,有些線程默認就是后台線程——前台線程創建的子線程默認是前台線程,后台線程創建的子線程默認是后台線程
前台線程死亡后,JVM會通知后台線程死亡,但從它接收指令到做出響應,需要一定時間。而且要將某個線程設置為后台線程,必須在該線程啟動之前設置,也就是說setDaemon(true)必須在start()方法之前調用,否則會引發IllegaIThreadStateException異常。
三、線程睡眠---sleep
如果需要讓當前正在執行的線程暫停一段時,並進入阻塞狀態,則可以通過調用Thread類的靜態sleep()方法來實現。當當前線程調用sleep()方法進入阻塞狀態后,在其睡眠時間段內,該線程不會獲得執行的機會,即使系統中沒有其他可執行的線程,處於sleep()中的線程也不會執行,因此sleep()方法常用來暫停程序的執行。
下面程序調用sleep()方法來暫停主線程的執行,因為該程序只有一個主線程,當主線程進入睡眠后,系統沒有可執行的線程,所以可以看到程序在sleep()方法處暫停
public class SleepTest {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("當前時間: " + new Date());
// 調用sleep方法讓當前線程暫停1s。
Thread.sleep(1000);
}
}
}
上面程序中sleep()方法將當前執行的線程暫停1秒,運行上面程序,看到程序依次輸出10條字符串,輸出2條字符串之間的時間間隔為1秒。
四、線程讓步---yield()
yield()方法是一個和sleep()方法有點相似的方法,它也是Threard類提供的一個靜態方法,它也可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態。yield()只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能的情況是:當某個線程調用了yield()方法暫停之后,線程調度器又將其調度出來重新執行。
實際上,當某個線程調用了yield()方法暫停之后,只有優先級與當前線程相同,或者優先級比當前線程更高的處於就緒狀態的線程才會獲得執行的機會。下面程序使用yield()方法來讓當前正在執行的線程暫停。
-
public class YieldTest extends Thread {
-
public YieldTest(String name) {
-
super(name);
-
}
-
-
// 定義 run方法作為線程執行體
-
public void run() {
-
for (int i = 0; i < 50; i++) {
-
System.out.println(getName() + "" + i);
-
// 當 i等於20時,使用yield方法讓當前線程讓步
-
if (i == 20) {
-
Thread.yield();
-
}
-
}
-
}
-
-
public static void main(String[] args) throws Exception {
-
// 啟動兩條並發線程
-
YieldTest yt1 = new YieldTest(" 高級 ");
-
// 將 ty1線程設置成最高優先級
-
// yt1.setPriority(Thread.MAX_PRIORITY);
-
yt1.start();
-
YieldTest yt2 = new YieldTest(" 低級 ");
-
// 將 yt2線程設置成最低優先級
-
// yt2.setPriority(Thread.MIN_PRIORITY);
-
yt2.start();
-
}
-
}
運行結果:
高級 0 低級 0 高級 1 低級 1 高級 2 低級 2 高級 3 低級 3 高級 4 低級 4 高級 5 |
低級 5 高級 6 低級 6 高級 7 低級 7 高級 8 低級 8 高級 9 低級 9 高級 10 低級 10 |
高級 11 低級 11 高級 12 低級 12 高級 13 低級 13 高級 14 低級 14 高級 15 低級 15 高級 16 |
低級 16 高級 17 低級 17 高級 18 低級 18 高級 19 低級 19 高級 20 低級 20 高級 21 低級 21 |
高級 22 低級 22 高級 23 低級 23 高級 24 低級 24 高級 25 …… 低級 48 高級 49 低級 49 |
上面程序中調用的yield()靜態方法讓當前正在執行的線程暫停,讓系統線程調度器重新調度。由於程序中第21行、第25行代碼處於注釋狀態——即兩個線程的優先級完全一樣,所以當一個線程使用yield()方法暫停后,另一個線程就會開始執行。如果將YieldTest.java程序中兩行代碼的注釋取消,也就是為兩個線程分別設置不同的優先級,則程序的運行結果示。
高級 0 高級 1 高級 2 高級 3 高級 4 高級 5 高級 6 高級 7 高級 8 高級 9 高級 10 |
高級 11 高級 12 高級 13 高級 14 高級 15 高級 16 高級 17 高級 18 高級 19 高級 20 高級 21 |
高級 22 高級 23 高級 24 高級 25 高級 26 高級 27 高級 28 高級 29 高級 30 高級 31 高級 32 |
高級 33 高級 34 高級 35 高級 36 高級 37 高級 38 高級 39 高級 40 高級 41 高級 42 高級 43 |
高級 44 高級 45 高級 46 高級 47 高級 48 高級 49 低級 0 低級 1 ……… 低級 48 低級 49 |
關於sleep()方法和yield()方法的區別如下:
1. sleep()方法暫停當前線程后,會給其他線程執行機會,不會理會其他線程的優先級;但yield()方法只會給優先級相同,或優先級更高的線程執行機會
2. sleep()方法會將線程轉入阻塞狀態,直到經過阻塞時間才會轉入就緒狀態:而yield()不會將線程轉入阻塞狀態,它只是強制當前線程進入就緒狀態。因此完全有可能某個線程調用yield()方法暫停之后,立即再次獲得處理器資源被執行
3. sleep()方法聲明拋出了InterruptcdException異常,所以調用sleep()方法時要么捕捉該異常,要么顯式聲明拋出該異常;而yield()方法則沒有聲明拋出任何異常
4. sleep()方法比yield()方法有更好的可移植性,通常不建議使用yield()方法來控制並發線程的執行
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】。
如果,您希望更容易地發現我的新博客,不妨點擊一下左下角的【關注我】。
如果,您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是【Sunddenly】。本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利