接著前面的多線程(二)的內容,下面我們接著來探討多個線程創建之后,關于線程調度和管理的一些方法。
先來簡單介紹下線程調度###
對于計算機的CPU(以單核為例),在任意時刻只能執行一條指令,每個線程只有獲得CPU的使用權才能執行指令。當有多個處于可運行狀態的線程在等待CPU,JVM的一項任務就是負責線程的調度。JVM按照特定機制為多個線程分配CPU的使用權過程就是線程調度。
線程調度的兩種模型:分時調度模型和搶占式調度模型。
① 分時調度模型是指讓所有的線程輪流獲得cpu的使用權,并且平均分配每個線程占用的CPU的時間片,這個比較好理解。
② JVM采用搶占式調度模型,就是讓線程搶奪CPU資源,運行順序是不確定的,優先權高的線程,會有一定幾率優先占用CPU。處于運行狀態的線程會一直運行下去,直至它不得不放棄CPU。比如線程運行完畢、線程阻塞、運行被打斷。關于線程的多種運行狀態詳見:Java中的多線程(二)線程的創建及線程的生命周期。當線程被中斷時,CPU會保存當前線程的狀態,以備此線程被喚醒時繼續執行。
線程管理之設置線程優先級###
Java線程優先級共有10個級別,優先級較高的線程會獲得較多的運行機會,取值范圍是從1到10。如果小于1或大于10,則JDK拋出異常IllegalArgumentException()。Thread類有以下三個靜態常量:
① static final int MAX_PRIORITY :值為10,代表最高優先級。
② static final int MIN_PRIORITY :值為1,代表最低優先級。
③ static final int NORM_PRIORITY:值為5,代表默認優先級。
這里要注意的是:
① 并不是說優先級較高的線程一定會在優先級較低的線程之前運行,優先級高這里是指獲得較多的運行機會。
② 優先級高的線程會大部分先執行完,并不一定會全部執行完畢。
③ 子線程的優先級是跟父類優先級是一樣的。
設置和獲取優先級方法:
thread.setPriority(Thread.MIN_PRIORITY)和 thread.getPriority()
線程管理之守護線程###
我們在程序中創建的線程默認都是用戶線程(User Thread)。與用戶線程對應則是守護線程(Daemon Thread),也可稱之為后臺線程,它的作用就是為其它線程提供服務的。守護線程使用的情況較少,舉例來說:JVM的垃圾(GC)回收線程就是守護線程。
需要注意的是:
① 當所有的用戶線程都結束退出的時候,守護線程也就沒啥可服務的了,隨著線程的結束而結束。如果JVM只剩下守護線程,虛擬機就會退出。
② 守護線程會隨時中斷,因此不要在如輸入輸出流,數據庫連接等場合使用守護線程。
③ 守護線程并非是JVM內部可提供,我們自己可以根據需要來設定守護線程。可以通過isDaemon和setDaemon方法來判斷和設置一個線程為守護線程。
④ 守護線程必須在start方法前設置,否則會拋出IllegalThreadStateException異常。
⑤ 一個守護線程創建的子線程依然是守護線程。
package com.Dan;
public class Main {
public static void main(String[] args) {
// write your code here
DaemonThread daemonThread = new DaemonThread();
// 設置為守護線程
daemonThread.setDaemon(true);
daemonThread.start();
}
}
class DaemonThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + " start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
}
}
// Thread-0 start
// 子線程需要執行1秒,因為設置被設置為守護線程,因此主線程不會等待子線程執行結束,而是提前退出。
// 所有用戶線程都退出了,守護線程也就退出了,因此并沒有打印end。
線程管理之常用方法###
一些簡單方法#####
public static int activeCount():返回當前線程的線程組中活動線程的數目。
public static Thread currentThread():返回對當前正在執行的線程對象的引用。
public long getId():返回該線程的標識符,線程 ID 是唯一的。
public final void setName(String name):改變線程名稱。
public final String getName():返回該線程的名稱。
public String toString():返回該線程的字符串表示形式,包括線程名稱、優先級和線程組。
public void start():使該線程開始執行,Java 虛擬機調用該線程的 run 方法。
下面是一些比較重要的方法:
sleep()方法 線程睡眠#####
sleep方法作用就是讓當前線程休眠,交出CPU,讓CPU去執行其他的任務。當前線程是指this.currentThread()返回的線程。sleep方法使線程轉到阻塞狀態,當睡眠結束后,就轉為可運行狀態。
package com.Dan;
/**
* Created by daniel on 17/3/27.
*/
public class 多線程博客三 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主線程開始執行。");
TaskThread thread1 = new TaskThread("線程-A-");
TaskThread thread2 = new TaskThread("線程-B-");
thread1.start();
thread2.start();
System.out.println(Thread.currentThread().getName()+ "主線程運行結束。");
}
}
class TaskThread extends Thread{
private String name;
public TaskThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "子線程運行開始。");
for (int i = 0; i < 5; i++) {
System.out.println(name + "運行: " + i);
try {
sleep((long) Math.random() * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "子線程運行結束!");
}
}
// main主線程開始執行。
// Thread-0子線程運行開始。
// Thread-1子線程運行開始。
// 線程-B-運行: 0
// main主線程運行結束。
// 線程-A-運行: 0
// 線程-B-運行: 1
// 線程-A-運行: 1
// 線程-B-運行: 2
// 線程-A-運行: 2
// 線程-B-運行: 3
// 線程-A-運行: 3
// 線程-B-運行: 4
// 線程-A-運行: 4
// Thread-1子線程運行結束。
// Thread-0子線程運行結束。
// sleep方法使當前線程休眠,交出CPU,讓CPU去執行其他的任務。這里的主線程,早在子線程開始后就馬上結束了。
join()線程加入#####
在當前線程中調用另一個線程的join方法,則當前線程轉入阻塞狀態,直到另一個進程運行結束,當前線程再由阻塞轉為可運行狀態。如果調用此方法時,另一個線程已經運行完畢,那就接著運行當前線程。
package com.Dan;
/**
* Created by daniel on 17/3/27.
*/
public class ThreadJoin {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() { //匿名對象
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " 子線程 " + i);
}
}
});
thread.start();
for (int i = 0; i < 10; i++) {
if (i == 5) {
try {
thread.join(); // 此時調用Thread線程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 主線程 " + i);
}
}
}
// main 主線程 0
// main 主線程 1
// main 主線程 2
// main 主線程 3
// main 主線程 4
// Thread-0 子線程 0
// Thread-0 子線程 1
// Thread-0 子線程 2
// Thread-0 子線程 3
// Thread-0 子線程 4
// Thread-0 子線程 5
// Thread-0 子線程 6
// Thread-0 子線程 7
// Thread-0 子線程 8
// Thread-0 子線程 9
// main 主線程 5
// main 主線程 6
// main 主線程 7
// main 主線程 8
// main 主線程 9
yield()線程讓步#####
暫停當前正在運行的線程,把運行機會讓給其它的線程。這里的暫停,并不是讓線程轉到阻塞或等待狀態,而是返回可運行狀態,等待被調度運行。需要注意的是,此時讓步的線程是可運行狀態,它有可能會被再次運行。
package com.Dan;
/**
* Created by daniel on 17/3/24.
*/
public class JavaEveryDay0324 extends Thread{
public static void main(String[] args) {
JavaEveryDay0324 test1 = new JavaEveryDay0324();
JavaEveryDay0324 test2 = new JavaEveryDay0324();
test1.start();
test2.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 1");
yield();
System.out.println(Thread.currentThread().getName() + " 2");
}
}
// Thread-0 1
// Thread-1 1
// Thread-0 2
// Thread-1 2
// 線程test1在輸出1后,執行yield()方法進入可運行狀態,然后將CPU讓給線程test2。同樣線程test2運行輸出1后,執行yield()方法也進入了可運行狀態,將CPU又讓給線程test1,線程test1繼續執行,打印輸出2,然后線程test2執行輸出2。
// Thread-0 1
// Thread-0 2
// Thread-1 1
// Thread-1 2
// 也有可能出現這種情況,多運行幾次,肯定會有的。
// Thread-0 1
// Thread-1 1
// Thread-1 2
// Thread-0 2
// 又或者是這一種。只要記住它返回的是可運行狀態,不是阻塞,也不是等待。那么不同的結果就能解釋清楚了。
** interrupt()線程中斷信號**#####
interrupt():這里只談中斷信號,中斷線程后續博客會更新。它向線程發送一個中斷信號,強制結束調用該方法的線程當前狀態,讓線程在無限等待時(如死鎖時)能拋出異常。
package com.Dan;
/**
* Created by daniel on 17/3/27.
*/
public class ThreadInterrupt {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000000); // 別想醒過來了
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("拋異常嘍吆唉哈咦。");
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " 子線程 " + i);
}
}
});
thread.start();
for (int i = 0; i < 6; i++) {
if (i == 2) {
thread.interrupt(); // 終止當前線程的狀態,并拋出個異常: e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "輸出:" + i);
}
}
}
// main輸出:0
// main輸出:1
// 拋異常嘍吆唉哈咦。
// main輸出:2
// Thread-0 子線程 0
// Thread-0 子線程 1
// Thread-0 子線程 2
// Thread-0 子線程 3
// Thread-0 子線程 4
// Thread-0 子線程 5
// main輸出:3
// main輸出:4
// Thread-0 子線程 6
// Thread-0 子線程 7
// Thread-0 子線程 8
// Thread-0 子線程 9
// main輸出:5
** wait()線程等待**#####
這里先簡單介紹下wait方法,我們會在下一篇博客探討線程同步相關知識的時候,再來探討它。
在線程通信中,wait方法經常與notify和notifyAll方法一起使用。,他們并不是Thread類的方法,而是Object類的方法。
① wait:當達到某種狀態的時候,wait方法讓線程進入等待狀態,讓本線程休眠。線程自動釋放其占有的對象鎖,并等待notify。直到有其它線程調用對象的notify方法喚醒該線程,才能繼續獲取對象鎖,繼續運行。
② notify:喚醒一個正在等待當前對象鎖的線程,并讓它拿到對象鎖。
③ notifyAll:喚醒所有正在等待前對象鎖的線程。
需要注意的是:
① 在調用這3個方法的時候,當前線程必須獲得這個對象的鎖,也就是說這三個方法必須在synchronized(Obj){...}內部。
② 在調用notify方法后,并不會馬上釋放對象鎖,而是在synchronized(){}執行結束的時候,自動釋放鎖,JVM會隨機喚醒一個正在等待當前對象鎖的線程,讓他獲得對象鎖,喚醒線程。
** wait()與sleep()方法的區別**#####
① sleep方法是一個靜態方法,作用在當前線程上。而wait方法是一個實例方法,并且只能在其他線程調用本實例的notify方法時被喚醒。
② wait只能在線程同步環境中被調用,會釋放鎖。而sleep不限制使用環境,當在一個Synchronized塊中調用Sleep方法時,線程雖然休眠了,但是對象的鎖并木有被釋放,其他線程無法訪問這個對象。
③ sleep必須捕獲異常,而wait,notify和notifyAll則不需要。
④ 進入等待狀態的線程能夠被notify方法喚醒。sleep休眠時間到了,該線程不一定會立即執行,因為其它線程可能正在運行。
⑤ 如果你需要暫停某個線程一段特定的時間,就使用sleep方法。如果你想要實現線程間通信就使用wait方法。
** 關于 “調用yield()方法后,會選擇同等優先級的線程繼續執行。” 勘誤**#####
看到網上有人說調用yield()方法后,會選擇同等優先級或更高優先級的線程繼續執行。這個觀點是錯誤的。具體執行執行哪個線程是由JVM說了算。請看下面的例子,低優先級的也會被執行。
// 在上面方法基礎上重新修改的
package com.Dan;
/**
* Created by daniel on 17/3/24.
*/
public class JavaEveryDay0324 extends Thread{
public static void main(String[] args) {
JavaEveryDay0324 test3 = new JavaEveryDay0324();
JavaEveryDay0324 test1 = new JavaEveryDay0324();
JavaEveryDay0324 test2 = new JavaEveryDay0324();
test3.setPriority(1);
test2.setPriority(10);
test1.setPriority(5);
test3.start();
test1.start();
test2.start();
System.out.println("線程1ID: "+test1.getId()+" 線程2ID: "+test2.getId()+" 線程3ID: "+test3.getId());
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("線程ID為"+Thread.currentThread().getId()+": " + " 的第①次打印");
yield();
System.out.println("線程ID為"+Thread.currentThread().getId() +": " + " 的第②次打印");
}
}
}
// 線程ID為10: 的第①次打印
// 線程ID為11: 的第①次打印
// 線程ID為12: 的第①次打印
// 線程1ID: 11 線程2ID: 12 線程3ID: 10
// 線程ID為10: 的第②次打印
// 線程ID為10: 的第①次打印
// 線程ID為11: 的第②次打印
// 線程ID為11: 的第①次打印
// 線程ID為12: 的第②次打印
// 線程ID為12: 的第①次打印
// 線程ID為10: 的第②次打印
// 線程ID為10: 的第①次打印
// 線程ID為11: 的第②次打印
// 線程ID為11: 的第①次打印
// 線程ID為12: 的第②次打印
// 線程ID為12: 的第①次打印
// 線程ID為10: 的第②次打印
// 線程ID為12: 的第②次打印
// 線程ID為11: 的第②次打印
寫完嘍!ㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏ
知識重在總結和梳理,只有不斷地去學習并運用,才能化為自己的東西。當你能為別人講明白的時候,說明自己已經掌握了。
歡迎轉載,轉載請注明出處!
如果有錯誤的地方,或者有您的見解,還請不嗇賜教!
喜歡的話,麻煩點個贊!