事情的全部起因來自于這樣一個(gè)程序
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);
}
}
這是一個(gè)簡單的多線程下的計(jì)數(shù)器油够,用于說明volatile修飾的變量并不能完全解決多線程并發(fā)問題边翼,體現(xiàn)在這段代碼中就是最后打印的結(jié)果有可能<100须眷。
這篇博文的主題不是討論volatile關(guān)鍵字的用法竖瘾,而是你如果在linux下跑這段程序,會(huì)卡在死循環(huán)了出不來花颗,各種百度捕传,google,總算找到了問題扩劝,我們先來看一看庸论,簡單啟動(dòng)一個(gè)main程序時(shí),有多少個(gè)線程被創(chuàng)建呢棒呛?
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());
最后打印結(jié)果如下
好聂示,我們分別來看看這幾個(gè)線程都是干嘛用的,這部分內(nèi)容主要來自
http://ifeve.com/jvm-thread/条霜,可以去這個(gè)地址查看更多線程的信息
Attach Listener
Attach Listener線程是負(fù)責(zé)接收到外部的命令催什,而對(duì)該命令進(jìn)行執(zhí)行的并且吧結(jié)果返回給發(fā)送者。通常我們會(huì)用一些命令去要求jvm給我們一些反 饋信息宰睡,如:java -version、jmap气筋、jstack等等拆内。如果該線程在jvm啟動(dòng)的時(shí)候沒有初始化,那么宠默,則會(huì)在用戶第一次執(zhí)行jvm命令時(shí)麸恍,得到啟動(dòng)。
Signal Dispatcher
前面我們提到第一個(gè)Attach Listener線程的職責(zé)是接收外部jvm命令搀矫,當(dāng)命令接收成功后抹沪,會(huì)交給signal dispather線程去進(jìn)行分發(fā)到各個(gè)不同的模塊處理命令,并且返回處理結(jié)果瓤球。signal dispather線程也是在第一次接收外部jvm命令時(shí)融欧,進(jìn)行初始化工作。
Finalizer
這個(gè)線程也是在main線程之后創(chuàng)建的卦羡,其優(yōu)先級(jí)為10噪馏,主要用于在垃圾收集前麦到,調(diào)用對(duì)象的finalize()方法;關(guān)于Finalizer線程的幾點(diǎn):
1. 只有當(dāng)開始一輪垃圾收集時(shí)欠肾,才會(huì)開始調(diào)用finalize()方法瓶颠;因此并不是所有對(duì)象的finalize()方法都會(huì)被執(zhí)行;
2. 該線程也是daemon線程刺桃,因此如果虛擬機(jī)中沒有其他非daemon線程粹淋,不管該線程有沒有執(zhí)行完finalize()方法,JVM也會(huì)退出瑟慈;
3. JVM在垃圾收集時(shí)會(huì)將失去引用的對(duì)象包裝成Finalizer對(duì)象(Reference的實(shí)現(xiàn))桃移,并放入ReferenceQueue,由Finalizer線程來處理封豪;最后將該Finalizer對(duì)象的引用置為null谴轮,由垃圾收集器來回收;
4. JVM為什么要單獨(dú)用一個(gè)線程來執(zhí)行finalize()方法呢吹埠?如果JVM的垃圾收集線程自己來做第步,很有可能由于在finalize()方法中誤操作導(dǎo)致GC線程停止或不可控,這對(duì)GC線程來說是一種災(zāi)難缘琅;
Reference Handler
VM在創(chuàng)建main線程后就創(chuàng)建Reference Handler線程粘都,其優(yōu)先級(jí)最高,為10刷袍,它主要用于處理引用對(duì)象本身(軟引用翩隧、弱引用、虛引用)的垃圾回收問題呻纹。
Monitor Ctrl-Break
這個(gè)線程我也不是很明白是干什么用的堆生,oracle官網(wǎng)有詳細(xì)信息,大家可以去看看
詳細(xì)鏈接
那問題來了雷酪,在linux下雖然創(chuàng)建了5個(gè)線程淑仆,但是當(dāng)前活動(dòng)線程只有兩個(gè),main和Monitor Ctrl-Break哥力,這就導(dǎo)致了蔗怠,我們在等待所有子線程結(jié)束后的那句判斷代碼應(yīng)該是>2而不是>1!!!
while (Thread.activeCount() > 2){
System.out.println(Thread.activeCount());
Thread.yield();
}
結(jié)論
windows下這個(gè)Monitor Ctrl-Break是不算在活動(dòng)線程的,所以這樣大于1是可以執(zhí)行的吩跋,但是linux下應(yīng)該是 大于2