啟動一個最簡單的Java main程式時,有多少個執行緒被建立

weixin_34321977發表於2017-05-31

事情的全部起因來自於這樣一個程式

public class VolatileTest {
    public static volatile int race = 0;

    public static void increase(){
        race++;
    }

    private static final int THREADS_COUNT = 10;

    public static void main(String[] args) {

        Thread[] threads = new Thread[THREADS_COUNT];

        for (int i = 0;i < THREADS_COUNT;i++){
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0;i < 10;i++){
                        increase();
                    }
                }
            });
            threads[i].start();
        }

        while (Thread.activeCount() > 1){
            System.out.println(Thread.activeCount());
            Thread.yield();
        }
        System.out.println(race);
    }
}

這是一個簡單的多執行緒下的計數器,用於說明volatile修飾的變數並不能完全解決多執行緒併發問題,體現在這段程式碼中就是最後列印的結果有可能<100。

這篇博文的主題不是討論volatile關鍵字的用法,而是你如果在linux下跑這段程式,會卡在死迴圈了出不來,各種百度,google,總算找到了問題,我們先來看一看,簡單啟動一個main程式時,有多少個執行緒被建立呢?


        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);

        for (ThreadInfo info : threadInfos
             ) {
            System.out.println("[" + info.getThreadId() + "]" + info.getThreadName());
        }

        System.out.println(Thread.activeCount());

最後列印結果如下

6253588-29148ee5fc8c0b1a.png
main執行緒.png

好,我們分別來看看這幾個執行緒都是幹嘛用的,這部分內容主要來自
http://ifeve.com/jvm-thread/,可以去這個地址檢視更多執行緒的資訊

Attach Listener

Attach Listener執行緒是負責接收到外部的命令,而對該命令進行執行的並且吧結果返回給傳送者。通常我們會用一些命令去要求jvm給我們一些反 饋資訊,如:java -version、jmap、jstack等等。如果該執行緒在jvm啟動的時候沒有初始化,那麼,則會在使用者第一次執行jvm命令時,得到啟動。

Signal Dispatcher

前面我們提到第一個Attach Listener執行緒的職責是接收外部jvm命令,當命令接收成功後,會交給signal dispather執行緒去進行分發到各個不同的模組處理命令,並且返回處理結果。signal dispather執行緒也是在第一次接收外部jvm命令時,進行初始化工作。

Finalizer

這個執行緒也是在main執行緒之後建立的,其優先順序為10,主要用於在垃圾收集前,呼叫物件的finalize()方法;關於Finalizer執行緒的幾點:

1. 只有當開始一輪垃圾收集時,才會開始呼叫finalize()方法;因此並不是所有物件的finalize()方法都會被執行;
  2. 該執行緒也是daemon執行緒,因此如果虛擬機器中沒有其他非daemon執行緒,不管該執行緒有沒有執行完finalize()方法,JVM也會退出;
  3. JVM在垃圾收集時會將失去引用的物件包裝成Finalizer物件(Reference的實現),並放入ReferenceQueue,由Finalizer執行緒來處理;最後將該Finalizer物件的引用置為null,由垃圾收集器來回收;
  4. JVM為什麼要單獨用一個執行緒來執行finalize()方法呢?如果JVM的垃圾收集執行緒自己來做,很有可能由於在finalize()方法中誤操作導致GC執行緒停止或不可控,這對GC執行緒來說是一種災難;

Reference Handler

VM在建立main執行緒後就建立Reference Handler執行緒,其優先順序最高,為10,它主要用於處理引用物件本身(軟引用、弱引用、虛引用)的垃圾回收問題。

Monitor Ctrl-Break

這個執行緒我也不是很明白是幹什麼用的,oracle官網有詳細資訊,大家可以去看看
詳細連結

那問題來了,在linux下雖然建立了5個執行緒,但是當前活動執行緒只有兩個,main和Monitor Ctrl-Break,這就導致了,我們在等待所有子執行緒結束後的那句判斷程式碼應該是>2而不是>1!!!

while (Thread.activeCount() > 2){
            System.out.println(Thread.activeCount());
            Thread.yield();
        }

結論

windows下這個Monitor Ctrl-Break是不算在活動執行緒的,所以這樣大於1是可以執行的,但是linux下應該是 大於2

相關文章