接著前面的多線程(二)的內容嘱蛋,下面我們接著來探討多個線程創(chuàng)建之后蚯姆,關于線程調度和管理的一些方法。
先來簡單介紹下線程調度###
對于計算機的CPU(以單核為例)洒敏,在任意時刻只能執(zhí)行一條指令龄恋,每個線程只有獲得CPU的使用權才能執(zhí)行指令。當有多個處于可運行狀態(tài)的線程在等待CPU凶伙,JVM的一項任務就是負責線程的調度郭毕。JVM按照特定機制為多個線程分配CPU的使用權過程就是線程調度。
線程調度的兩種模型:分時調度模型和搶占式調度模型函荣。
① 分時調度模型是指讓所有的線程輪流獲得cpu的使用權,并且平均分配每個線程占用的CPU的時間片显押,這個比較好理解。
② JVM采用搶占式調度模型傻挂,就是讓線程搶奪CPU資源乘碑,運行順序是不確定的,優(yōu)先權高的線程金拒,會有一定幾率優(yōu)先占用CPU兽肤。處于運行狀態(tài)的線程會一直運行下去,直至它不得不放棄CPU殖蚕。比如線程運行完畢轿衔、線程阻塞、運行被打斷睦疫。關于線程的多種運行狀態(tài)詳見:Java中的多線程(二)線程的創(chuàng)建及線程的生命周期。當線程被中斷時鞭呕,CPU會保存當前線程的狀態(tài)蛤育,以備此線程被喚醒時繼續(xù)執(zhí)行。
線程管理之設置線程優(yōu)先級###
Java線程優(yōu)先級共有10個級別,優(yōu)先級較高的線程會獲得較多的運行機會瓦糕,取值范圍是從1到10底洗。如果小于1或大于10,則JDK拋出異常IllegalArgumentException()咕娄。Thread類有以下三個靜態(tài)常量:
① static final int MAX_PRIORITY :值為10亥揖,代表最高優(yōu)先級。
② static final int MIN_PRIORITY :值為1圣勒,代表最低優(yōu)先級费变。
③ static final int NORM_PRIORITY:值為5,代表默認優(yōu)先級圣贸。
這里要注意的是:
① 并不是說優(yōu)先級較高的線程一定會在優(yōu)先級較低的線程之前運行挚歧,優(yōu)先級高這里是指獲得較多的運行機會。
② 優(yōu)先級高的線程會大部分先執(zhí)行完吁峻,并不一定會全部執(zhí)行完畢滑负。
③ 子線程的優(yōu)先級是跟父類優(yōu)先級是一樣的。
設置和獲取優(yōu)先級方法:
thread.setPriority(Thread.MIN_PRIORITY)和 thread.getPriority()
線程管理之守護線程###
我們在程序中創(chuàng)建的線程默認都是用戶線程(User Thread)用含。與用戶線程對應則是守護線程(Daemon Thread)矮慕,也可稱之為后臺線程,它的作用就是為其它線程提供服務的啄骇。守護線程使用的情況較少凡傅,舉例來說:JVM的垃圾(GC)回收線程就是守護線程。
需要注意的是:
① 當所有的用戶線程都結束退出的時候肠缔,守護線程也就沒啥可服務的了夏跷,隨著線程的結束而結束。如果JVM只剩下守護線程明未,虛擬機就會退出槽华。
② 守護線程會隨時中斷,因此不要在如輸入輸出流趟妥,數(shù)據(jù)庫連接等場合使用守護線程猫态。
③ 守護線程并非是JVM內部可提供,我們自己可以根據(jù)需要來設定守護線程披摄∏籽可以通過isDaemon和setDaemon方法來判斷和設置一個線程為守護線程。
④ 守護線程必須在start方法前設置疚膊,否則會拋出IllegalThreadStateException異常义辕。
⑤ 一個守護線程創(chuàng)建的子線程依然是守護線程。
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
// 子線程需要執(zhí)行1秒寓盗,因為設置被設置為守護線程灌砖,因此主線程不會等待子線程執(zhí)行結束璧函,而是提前退出。
// 所有用戶線程都退出了基显,守護線程也就退出了蘸吓,因此并沒有打印end。
線程管理之常用方法###
一些簡單方法#####
public static int activeCount():返回當前線程的線程組中活動線程的數(shù)目撩幽。
public static Thread currentThread():返回對當前正在執(zhí)行的線程對象的引用库继。
public long getId():返回該線程的標識符,線程 ID 是唯一的窜醉。
public final void setName(String name):改變線程名稱宪萄。
public final String getName():返回該線程的名稱。
public String toString():返回該線程的字符串表示形式酱虎,包括線程名稱雨膨、優(yōu)先級和線程組。
public void start():使該線程開始執(zhí)行读串,Java 虛擬機調用該線程的 run 方法聊记。
下面是一些比較重要的方法:
sleep()方法 線程睡眠#####
sleep方法作用就是讓當前線程休眠,交出CPU恢暖,讓CPU去執(zhí)行其他的任務排监。當前線程是指this.currentThread()返回的線程。sleep方法使線程轉到阻塞狀態(tài)杰捂,當睡眠結束后舆床,就轉為可運行狀態(tài)。
package com.Dan;
/**
* Created by daniel on 17/3/27.
*/
public class 多線程博客三 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主線程開始執(zhí)行嫁佳。");
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主線程開始執(zhí)行蒿往。
// 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去執(zhí)行其他的任務。這里的主線程饥漫,早在子線程開始后就馬上結束了榨呆。
join()線程加入#####
在當前線程中調用另一個線程的join方法,則當前線程轉入阻塞狀態(tài)趾浅,直到另一個進程運行結束愕提,當前線程再由阻塞轉為可運行狀態(tài)馒稍。如果調用此方法時皿哨,另一個線程已經(jīng)運行完畢浅侨,那就接著運行當前線程。
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()線程讓步#####
暫停當前正在運行的線程证膨,把運行機會讓給其它的線程如输。這里的暫停,并不是讓線程轉到阻塞或等待狀態(tài)央勒,而是返回可運行狀態(tài)不见,等待被調度運行。需要注意的是崔步,此時讓步的線程是可運行狀態(tài)稳吮,它有可能會被再次運行。
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后井濒,執(zhí)行yield()方法進入可運行狀態(tài)灶似,然后將CPU讓給線程test2。同樣線程test2運行輸出1后瑞你,執(zhí)行yield()方法也進入了可運行狀態(tài)酪惭,將CPU又讓給線程test1,線程test1繼續(xù)執(zhí)行者甲,打印輸出2春感,然后線程test2執(zhí)行輸出2。
// Thread-0 1
// Thread-0 2
// Thread-1 1
// Thread-1 2
// 也有可能出現(xiàn)這種情況虏缸,多運行幾次鲫懒,肯定會有的。
// Thread-0 1
// Thread-1 1
// Thread-1 2
// Thread-0 2
// 又或者是這一種刽辙。只要記住它返回的是可運行狀態(tài)窥岩,不是阻塞,也不是等待扫倡。那么不同的結果就能解釋清楚了谦秧。
** interrupt()線程中斷信號**#####
interrupt():這里只談中斷信號,中斷線程后續(xù)博客會更新撵溃。它向線程發(fā)送一個中斷信號疚鲤,強制結束調用該方法的線程當前狀態(tài),讓線程在無限等待時(如死鎖時)能拋出異常缘挑。
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(); // 終止當前線程的狀態(tài),并拋出個異常: 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方法經(jīng)常與notify和notifyAll方法一起使用鹅心。,他們并不是Thread類的方法纺荧,而是Object類的方法旭愧。
① wait:當達到某種狀態(tài)的時候,wait方法讓線程進入等待狀態(tài)宙暇,讓本線程休眠输枯。線程自動釋放其占有的對象鎖,并等待notify占贫。直到有其它線程調用對象的notify方法喚醒該線程桃熄,才能繼續(xù)獲取對象鎖,繼續(xù)運行型奥。
② notify:喚醒一個正在等待當前對象鎖的線程瞳收,并讓它拿到對象鎖。
③ notifyAll:喚醒所有正在等待前對象鎖的線程桩引。
需要注意的是:
① 在調用這3個方法的時候缎讼,當前線程必須獲得這個對象的鎖,也就是說這三個方法必須在synchronized(Obj){...}內部坑匠。
② 在調用notify方法后血崭,并不會馬上釋放對象鎖,而是在synchronized(){}執(zhí)行結束的時候厘灼,自動釋放鎖夹纫,JVM會隨機喚醒一個正在等待當前對象鎖的線程,讓他獲得對象鎖设凹,喚醒線程刊苍。
** wait()與sleep()方法的區(qū)別**#####
① sleep方法是一個靜態(tài)方法搜立,作用在當前線程上桩匪。而wait方法是一個實例方法捐川,并且只能在其他線程調用本實例的notify方法時被喚醒。
② wait只能在線程同步環(huán)境中被調用奋姿,會釋放鎖锄开。而sleep不限制使用環(huán)境,當在一個Synchronized塊中調用Sleep方法時称诗,線程雖然休眠了萍悴,但是對象的鎖并木有被釋放,其他線程無法訪問這個對象。
③ sleep必須捕獲異常癣诱,而wait计维,notify和notifyAll則不需要。
④ 進入等待狀態(tài)的線程能夠被notify方法喚醒撕予。sleep休眠時間到了鲫惶,該線程不一定會立即執(zhí)行,因為其它線程可能正在運行嗅蔬。
⑤ 如果你需要暫停某個線程一段特定的時間剑按,就使用sleep方法疾就。如果你想要實現(xiàn)線程間通信就使用wait方法澜术。
** 關于 “調用yield()方法后,會選擇同等優(yōu)先級的線程繼續(xù)執(zhí)行猬腰∧穹希” 勘誤**#####
看到網(wǎng)上有人說調用yield()方法后,會選擇同等優(yōu)先級或更高優(yōu)先級的線程繼續(xù)執(zhí)行姑荷。這個觀點是錯誤的盒延。具體執(zhí)行執(zhí)行哪個線程是由JVM說了算。請看下面的例子鼠冕,低優(yōu)先級的也會被執(zhí)行添寺。
// 在上面方法基礎上重新修改的
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: 的第②次打印
寫完嘍!ㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏㄟ(▔,▔)ㄏ
知識重在總結和梳理懈费,只有不斷地去學習并運用计露,才能化為自己的東西。當你能為別人講明白的時候憎乙,說明自己已經(jīng)掌握了票罐。
歡迎轉載,轉載請注明出處泞边!
如果有錯誤的地方该押,或者有您的見解,還請不嗇賜教阵谚!
喜歡的話蚕礼,麻煩點個贊!
Java中的多線程(一)多線程基礎之進程梢什、線程奠蹬、并發(fā)、并行绳矩。
Java中的多線程(二)線程的創(chuàng)建及線程的生命周期