寫在開頭
面試官:小夥子請聊一聊Java中的精靈執行緒?
我:什麼?精靈執行緒?啥時候精靈執行緒?
面試官:精靈執行緒沒聽過?那守護執行緒呢?
我:守護執行緒知道,就是為普通執行緒服務的執行緒嘛。
面試官:沒了?守護執行緒的特點,怎麼使用,需要注意啥,Java中經典的守護執行緒都有啥?
我:不知道。。。
這的天,面試一個10K的工作,上來先整個精靈執行緒,直接把人整蒙了,難道提及Java多執行緒的時候,問的不應該是執行緒、執行緒池、併發衝突解決方案、如何加鎖,以及各種鎖的知識點嗎?上來整個偏門的守護執行緒,這是出心的不想要啊。
何為守護執行緒
上面這段內容是在牛客上看到的,說實話這位面試官問的這內容確實主要一個:東西沒用,但你得知道!可如果說他問的真是離譜嗎?也算不上,精靈執行緒我們很少聽到,但守護執行緒我們在學習Java執行緒的時候肯定有所耳聞!那麼今天我們就一起來小酌一下這個 “ 守護執行緒
”
Java中的執行緒分為2種:使用者執行緒和守護執行緒
使用者執行緒又叫普通執行緒,是我們驅動業務邏輯運轉的核心;而守護執行緒,顧名思義,是守護使用者執行緒的一種執行緒,執行在後臺提供通用服務,因此也叫後臺執行緒或者精靈執行緒。
守護執行緒的使用場景
那在Java中這個守護執行緒都有什麼實際用處,或者說應用場景呢?
- GC垃圾回收執行緒:這是JVM中非常經典的一個守護執行緒,它始終以低階別狀態執行,用於實時監控和管理系統中的可回收資源,一旦我們的系統沒有任何執行的使用者執行緒時,程式也就不會再產生垃圾,這時,無事可做的垃圾回收執行緒會自動結束。
- 應用指標統計:部分服務可以透過守護執行緒來採取應用指標,服務結束則停止採集。
怎麼設定守護執行緒
那我們在程式碼中,如何將一個執行緒設定為守護執行緒呢?咱們可以透過在 start 執行緒之前呼叫執行緒的 setDaemon(true) 方法,將一個執行緒設定為守護執行緒,來看一下下面的這個demo。
【程式碼例項1】
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread("守護執行緒"){
@Override
public void run() {
int i = 0;
while (i <= 4){
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
super.run();
}
};
Thread thread2 = new Thread("使用者執行緒"){
@Override
public void run() {
int i = 0;
while (i < 2){
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
super.run();
}
};
//setDaemon, 不設定則預設false
thread1.setDaemon(true);//設定thread1為守護執行緒
thread2.setDaemon(false);//設定thread2為普通執行緒
thread1.start();
thread2.start();
}
}
輸出:
守護執行緒:1
使用者執行緒:1
使用者執行緒:2
守護執行緒:2
這段測試程式碼中,我們透過thread1.setDaemon(true)將執行緒1設定成了一個守護執行緒(false為普通執行緒),使用者執行緒的迴圈次數為2,使用者執行緒的迴圈次數為4,但當程式中的使用者執行緒執行完之後,守護執行緒並沒有繼續向下迴圈,而是隨著使用者執行緒的結束而自我終止了。
守護執行緒的優先順序
看到網上很多博文提到了守護執行緒的優先順序問題,都說守護執行緒的優先順序比較低,那我們透過一段測試用例看一下真實情況。
【程式碼例項2】
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread("守護執行緒"){
@Override
public void run() {
int i = 0;
while (i <= 4){
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i+"-優先順序:" +Thread.currentThread().getPriority());
}
super.run();
}
};
Thread thread2 = new Thread("使用者執行緒"){
@Override
public void run() {
int i = 0;
while (i < 2){
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i+"-優先順序:" +Thread.currentThread().getPriority());
}
super.run();
}
};
//setDaemon, 不設定則預設false
thread1.setDaemon(true);//設定thread1為守護執行緒
thread2.setDaemon(false);//設定thread2為普通執行緒
thread1.start();
thread2.start();
for (int i = 0; i <5 ; i++) {
System.out.println("主執行緒:"+i+"-優先順序:" +Thread.currentThread().getPriority());
}
}
}
輸出:
主執行緒:0-優先順序:5
主執行緒:1-優先順序:5
主執行緒:2-優先順序:5
主執行緒:3-優先順序:5
主執行緒:4-優先順序:5
使用者執行緒:1-優先順序:5
守護執行緒:1-優先順序:5
使用者執行緒:2-優先順序:5
守護執行緒:2-優先順序:5
這個測試結果是不是出乎意料?無論是主執行緒還是普通的使用者執行緒,又或者說守護執行緒,他們的優先順序都是5,優先順序竟然都一樣!
我們知道所謂的執行緒就是CPU 排程和分派的基本單位,根據優先順序不同,來決定獲取CPU時間片的先後順序,因為主執行緒啟動時,其他執行緒還沒有啟動,所以這時候它最先獲得CPU排程許可權;
又因為其他執行緒存在休眠時間,這個時間段上足夠主執行緒執行完畢。主執行緒執行完後,使用者執行緒和守護執行緒互相搶佔CPU資源,交錯執行,直至程式中沒有普通執行緒為止!若沒有休眠時間,且迴圈次數足夠多時,我們可以看到主執行緒、守護執行緒、使用者執行緒都競爭CPU時間片,呈現交錯執行的結果!
注意事項
在設定執行緒為守護執行緒的時候要注意一個事情,那就是當 start(); 放到 setDaemon(true); 之前,程式丟擲IllegalThreadStateException。如下圖:
原因是 setDaemon(true)原始碼中,有一個isAlive()的判斷,判斷當前執行緒的狀態是否為活躍執行緒,若是則丟擲異常,我們不能修改一個正在執行中的執行緒!
【原始碼解析1】
public final void setDaemon(boolean on) {
checkAccess();
//執行緒已經啟動後,不可修改,否則丟擲非法執行緒狀態異常
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
總結
OK,寫到這裡,關於守護執行緒的內容就講完了,我們從什麼是守護執行緒,守護執行緒的使用場景,優先順序,注意事項等方面,進行了全面的介紹。
其實說實話,在我們日後工作中,很少直接使用上守護執行緒,所以它看似沒那麼重要,但在很多Java多執行緒相關的書籍中絕對都有提及,很多小夥伴在學習的過程中認為這個點不重要,也就相當然的忽略了,但遇到變態的面試官,專門挑揀一些偏僻的知識點考你時,難免陷入尷尬,所以希望藉助這個考題,大家能夠在日後更細心的學習哈。
結尾彩蛋
如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!