Java 多線程系列第 7 篇。java
這篇咱們來說講線程的另外一個特性:守護線程 or 用戶線程?設計模式
咱們先來看看 Thread.setDaemon()
方法的註釋,以下所示。多線程
1. Marks this thread as either a daemon thread or a user thread.併發
裏面提到了 3 點信息,一一來作下解釋:異步
把 Java 線程分紅 2 類,一類是用戶線程,也就是咱們建立線程時,默認的一類線程,屬性 daemon = false
;另外一類是守護線程,當咱們設置 daemon = true
時,就是這類線程。ide
二者的通常關係是:用戶線程就是運行在前臺的線程,守護線程就是運行在後臺的線程,通常狀況下,守護線程是爲用戶線程提供一些服務。好比在 Java 中,咱們常說的 GC 內存回收線程就是守護線程。學習
上面第二點翻譯過來是:當全部用戶線程都執行完,只存在守護線程在運行時,JVM 就退出。看了網上資料以及一些書籍,全都有這句話,可是也都只是有這句話,沒有講明是爲啥,好像這句話就成了定理,不須要證實的樣子。既然咱最近搭建了 JVM Debug 環境,那就得來查個究竟。(查得好辛苦,花了好久的時間才查出來)測試
咱們看到 JVM 源碼 thread.cpp
文件,這裏是實現線程的代碼。咱們經過上面那句話,說明是有一個地方監測着當前非守護線程的數量,否則怎麼知道如今只剩下守護線程呢?頗有多是在移除線程的方法裏面,跟着這個思路,咱們看看該文件的 remove()
方法。代碼以下。ui
/**
* 移除線程 p
*/
void Threads::remove(JavaThread* p, bool is_daemon) {
// Reclaim the ObjectMonitors from the omInUseList and omFreeList of the moribund thread.
ObjectSynchronizer::omFlush(p);
/**
* 建立一個監控鎖對象 ml
*/
// Extra scope needed for Thread_lock, so we can check
// that we do not remove thread without safepoint code notice
{ MonitorLocker ml(Threads_lock);
assert(ThreadsSMRSupport::get_java_thread_list()->includes(p), "p must be present");
// Maintain fast thread list
ThreadsSMRSupport::remove_thread(p);
// 當前線程數減 1
_number_of_threads--;
if (!is_daemon) {
/**
* 非守護線程數量減 1
*/
_number_of_non_daemon_threads--;
/**
* 當非守護線程數量爲 1 時,喚醒在 destroy_vm() 方法等待的線程
*/
// Only one thread left, do a notify on the Threads_lock so a thread waiting
// on destroy_vm will wake up.
if (number_of_non_daemon_threads() == 1) {
ml.notify_all();
}
}
/**
* 移除掉線程
*/
ThreadService::remove_thread(p, is_daemon);
// Make sure that safepoint code disregard this thread. This is needed since
// the thread might mess around with locks after this point. This can cause it
// to do callbacks into the safepoint code. However, the safepoint code is not aware
// of this thread since it is removed from the queue.
p->set_terminated_value();
} // unlock Threads_lock
// Since Events::log uses a lock, we grab it outside the Threads_lock
Events::log(p, "Thread exited: " INTPTR_FORMAT, p2i(p));
}複製代碼
我在裏面加了一些註釋,能夠發現,果真是咱們想的那樣,裏面有記錄着非守護線程的數量,並且當非守護線程爲 1 時,就會喚醒在 destory_vm()
方法裏面等待的線程,咱們確認已經找到 JVM 在非守護線程數爲 1 時會觸發喚醒監控 JVM 退出的線程代碼。緊接着咱們看看 destory_vm()
代碼,一樣是在 thread.cpp
文件下。this
bool Threads::destroy_vm() {
JavaThread* thread = JavaThread::current();
#ifdef ASSERT
_vm_complete = false;
#endif
/**
* 等待本身是最後一個非守護線程條件
*/
// Wait until we are the last non-daemon thread to execute
{ MonitorLocker nu(Threads_lock);
while (Threads::number_of_non_daemon_threads() > 1)
/**
* 非守護線程數大於 1,則一直等待
*/
// This wait should make safepoint checks, wait without a timeout,
// and wait as a suspend-equivalent condition.
nu.wait(0, Mutex::_as_suspend_equivalent_flag);
}
/**
* 下面代碼是關閉 VM 的邏輯
*/
EventShutdown e;
if (e.should_commit()) {
e.set_reason("No remaining non-daemon Java threads");
e.commit();
}
...... 省略餘下代碼
}複製代碼
咱們這裏看到當非守護線程數量大於 1 時,就一直等待,直到剩下一個非守護線程時,就會在線程執行完後,退出 JVM。這時候又有一個點須要定位,何時調用 destroy_vm()
方法呢?仍是經過查看代碼以及註釋,發現是在 main()
方法執行完成後觸發的。
在 java.c
文件的 JavaMain()
方法裏面,最後執行完調用了 LEAVE()
方法,該方法調用了 (*vm)->DestroyJavaVM(vm);
來觸發 JVM 退出,最終調用 destroy_vm()
方法。
#define LEAVE() \
do { \
if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { \
JLI_ReportErrorMessage(JVM_ERROR2); \
ret = 1; \
} \
if (JNI_TRUE) { \
(*vm)->DestroyJavaVM(vm); \
return ret; \
} \
} while (JNI_FALSE)複製代碼
因此咱們也知道了,爲啥 main 線程能夠比子線程先退出?雖然 main 線程退出前調用了 destroy_vm()
方法,可是在 destroy_vm()
方法裏面等待着非守護線程執行完,子線程若是是非守護線程,則 JVM 會一直等待,不會當即退出。
咱們對這個點總結一下:Java 程序在 main 線程執行退出時,會觸發執行 JVM 退出操做,可是 JVM 退出方法 destroy_vm()
會等待全部非守護線程都執行完,裏面是用變量 numberofnondaemonthreads 統計非守護線程的數量,這個變量在新增線程和刪除線程時會作增減操做。
另外衍生一點就是:當 JVM 退出時,全部還存在的守護線程會被拋棄,既不會執行 finally 部分代碼,也不會執行 stack unwound 操做(也就是也不會 catch 異常)。這個很明顯,JVM 都退出了,守護線程天然退出了,固然這是守護線程的一個特性。
這個比較好理解,就是線程是用戶線程仍是守護線程,在線程還未啓動時就得肯定。在調用 start()
方法以前,還只是個對象,沒有映射到 JVM 中的線程,這個時候能夠修改 daemon
屬性,調用 start()
方法以後,JVM 中就有一個線程映射這個線程對象,因此不能作修改了。
這個咱就不用寫代碼來驗證了,直接看 Thread 源代碼構造方法裏面就能夠知道,代碼以下所示。
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...省略一堆代碼
this.daemon = parent.isDaemon();
...省略一堆代碼
}複製代碼
看到不少書籍和資料都這麼說,我也很懷疑。因此寫了下面代碼來測試是否是守護線程優先級比用戶線程低?
public class TestDaemon {
static AtomicLong daemonTimes = new AtomicLong(0);
static AtomicLong userTimes = new AtomicLong(0);
public static void main(String[] args) {
int count = 2000;
List<MyThread> threads = new ArrayList<>(count);
for (int i = 0; i < count; i ++) {
MyThread userThread = new MyThread();
userThread.setDaemon(false);
threads.add(userThread);
MyThread daemonThread = new MyThread();
daemonThread.setDaemon(true);
threads.add(daemonThread);
}
for (int i = 0; i < count; i++) {
threads.get(i).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("daemon 統計:" + daemonTimes.get());
System.out.println("user 統計:" + userTimes.get());
System.out.println("daemon 和 user 相差時間:" + (daemonTimes.get() - userTimes.get()) + "ms");
}
static class MyThread extends Thread {
@Override
public void run() {
if (this.isDaemon()) {
daemonTimes.getAndAdd(System.currentTimeMillis());
} else {
userTimes.getAndAdd(System.currentTimeMillis());
}
}
}
}複製代碼
運行結果以下。
結果1:
daemon 統計:1570785465411405
user 統計:1570785465411570
daemon 和 user 相差時間:-165ms
結果2:
daemon 統計:1570786615081403
user 統計:1570786615081398
daemon 和 user 相差時間:5ms複製代碼
是否是很驚訝,竟然相差無幾,可是這個案例我也不能下定義說:守護線程和用戶線程優先級是同樣的。看了 JVM 代碼也沒找到守護線程優先級比用戶線程低,這個點仍是保持懷疑,有了解的朋友能夠留言說一些,互相交流學習。
總結一下這篇文章講解的點,一個是線程被分爲 2 種類型,一種是用戶線程,另外一種是守護線程;若是要把線程設置爲守護線程,須要在線程調用start()
方法前設置 daemon
屬性;還有從 JVM 源碼角度分析爲何當用戶線程都執行完的時候,JVM 會自動退出。接着講解了守護線程有繼承性,父線程是守護線程,那麼子線程默認就是守護線程;另外對一些書籍和資料所說的 守護線程優先級比用戶線程低 提出本身的疑問,並但願有了解的朋友能幫忙解答。
若是以爲這篇文章看了有收穫,麻煩點個在看
,支持一下,原創不易。
推薦閱讀
寫了那麼多年 Java 代碼,終於 debug 到 JVM 了
瞭解Java線程優先級,更要知道對應操做系統的優先級,否則會踩坑
後臺回覆『設計模式』能夠獲取《一故事一設計模式》電子書
以爲文章有用幫忙轉發&點贊,多謝朋友們!
本文由博客一文多發平臺 OpenWrite 發佈!