這篇咱們來說講線程的另外一個特性:守護線程 or 用戶線程?java
咱們先來看看 Thread.setDaemon() 方法的註釋,以下所示。ide
Marks this thread as either a daemon thread or a user thread.學習
The Java Virtual Machine exits when the only threads running are all daemon threads.測試
This method must be invoked before the thread is started.this
裏面提到了 3 點信息,一一來作下解釋:線程
官方特性翻譯
1. 用戶線程 or 守護線程?code
把 Java 線程分紅 2 類,一類是用戶線程,也就是咱們建立線程時,默認的一類線程,屬性 daemon = false;另外一類是守護線程,當咱們設置 daemon = true 時,就是這類線程。cdn
二者的通常關係是:用戶線程就是運行在前臺的線程,守護線程就是運行在後臺的線程,通常狀況下,守護線程是爲用戶線程提供一些服務。好比在 Java 中,咱們常說的 GC 內存回收線程就是守護線程。對象
2. JVM 與用戶線程共存亡
上面第二點翻譯過來是:當全部用戶線程都執行完,只存在守護線程在運行時,JVM 就退出。看了網上資料以及一些書籍,全都有這句話,可是也都只是有這句話,沒有講明是爲啥,好像這句話就成了定理,不須要證實的樣子。既然咱最近搭建了 JVM Debug 環境,那就得來查個究竟。(查得好辛苦,花了好久的時間才查出來)
咱們看到 JVM 源碼 thread.cpp 文件,這裏是實現線程的代碼。咱們經過上面那句話,說明是有一個地方監測着當前非守護線程的數量,否則怎麼知道如今只剩下守護線程呢?頗有多是在移除線程的方法裏面,跟着這個思路,咱們看看該文件的 remove() 方法。代碼以下。
/**
* 移除線程 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 文件下。
咱們這裏看到當非守護線程數量大於 1 時,就一直等待,直到剩下一個非守護線程時,就會在線程執行完後,退出 JVM。這時候又有一個點須要定位,何時調用 destroy_vm() 方法呢?仍是經過查看代碼以及註釋,發現是在 main() 方法執行完成後觸發的。
在 java.c 文件的 JavaMain() 方法裏面,最後執行完調用了 LEAVE() 方法,該方法調用了 (*vm)-DestroyJavaVM(vm); 來觸發 JVM 退出,最終調用 destroy_vm() 方法。
因此咱們也知道了,爲啥 main 線程能夠比子線程先退出?雖然 main 線程退出前調用了 destroy_vm() 方法,可是在 destroy_vm() 方法裏面等待着非守護線程執行完,子線程若是是非守護線程,則 JVM 會一直等待,不會當即退出。
咱們對這個點總結一下:Java 程序在 main 線程執行退出時,會觸發執行 JVM 退出操做,可是 JVM 退出方法 destroy_vm() 會等待全部非守護線程都執行完,裏面是用變量 number_of_non_daemon_threads 統計非守護線程的數量,這個變量在新增線程和刪除線程時會作增減操做。
另外衍生一點就是:當 JVM 退出時,全部還存在的守護線程會被拋棄,既不會執行 finally 部分代碼,也不會執行 stack unwound 操做(也就是也不會 catch 異常)。這個很明顯,JVM 都退出了,守護線程天然退出了,固然這是守護線程的一個特性。
3. 是男是女?生下來就註定了
這個比較好理解,就是線程是用戶線程仍是守護線程,在線程還未啓動時就得肯定。在調用 start() 方法以前,還只是個對象,沒有映射到 JVM 中的線程,這個時候能夠修改 daemon 屬性,調用 start() 方法以後,JVM 中就有一個線程映射這個線程對象,因此不能作修改了。
其餘的特性
1.守護線程屬性繼承自父線程
這個咱就不用寫代碼來驗證了,直接看 Thread 源代碼構造方法裏面就能夠知道,代碼以下所示。
2.守護線程優先級比用戶線程低
看到不少書籍和資料都這麼說,我也很懷疑。因此寫了下面代碼來測試是否是守護線程優先級比用戶線程低?
public class TestDaemon {
static AtomicLong daemonTimes = new AtomicLong(0);
static AtomicLong userTimes = new AtomicLong(0);
public static void main(String[] args) {
int count = 2000;
Listthreads = 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());
}
}
}
}
運行結果以下。
是否是很驚訝,竟然相差無幾,可是這個案例我也不能下定義說:守護線程和用戶線程優先級是同樣的。看了 JVM 代碼也沒找到守護線程優先級比用戶線程低,這個點仍是保持懷疑,有了解的朋友能夠留言說一些,互相交流學習。
總結
總結一下這篇文章講解的點,一個是線程被分爲 2 種類型,一種是用戶線程,另外一種是守護線程;若是要把線程設置爲守護線程,須要在線程調用start()方法前設置 daemon 屬性;還有從 JVM 源碼角度分析爲何當用戶線程都執行完的時候,JVM 會自動退出。接着講解了守護線程有繼承性,父線程是守護線程,那麼子線程默認就是守護線程;另外對一些書籍和資料所說的 守護線程優先級比用戶線程低 提出本身的疑問,並但願有了解的朋友能幫忙解答。