类初始化与并发问题

这个案例是2009年中文站线上曾发生过的事情,实际情况比较复杂,简化后我们可以用下面的例子做demo

// 定义一个锁对象
class Lock {} 

// 在这个类的静态构造块里做一些事情
class Danger {
    static {
        System.out.println("clinit begin...");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
        }

        synchronized (Lock.class) { 
            System.out.println("clinit done!");
        }
    } 
}

public class Test {
    public static void main(String[] args) {
        // 启动新线程
        new Thread() { 
            public void run() {
                synchronized (Lock.class) { 
                    System.out.println("new thread start!");
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                    } 
                    new Danger();
                }
                System.out.println("new thread end!"); 

            }
        }.start();

        // 当前线程sleep 500毫秒
        try {
            Thread.sleep(500);
        } catch (Exception e) {
        } 
        // 当前线程创建了Danger对象实例
        System.out.println(new Danger()); 
        // 会走到这里吗?
        System.out.println("DONE!");
    } 
}

运行上面的程序时,java进程挂在那儿不动了,我们通过jstack去观察的话,它先给出死锁检查是没有找到:

Deadlock Detection:
No deadlocks found.

然后看看两个线程的堆栈信息:

Thread 17643: (state = BLOCKED)
    - Test$1.run() @bci=24, line=28 (Interpreted frame)

Thread 17634: (state = BLOCKED)
    - Danger.<clinit>() @bci=24, line=12 (Interpreted frame)
    - Test.main(java.lang.String[]) @bci=23, line=41 (Interpreted frame)

虽然jvm没有把这两个线程看做是死锁,但实际情况是和死锁类似的。在jvm的实现规范里:

当多个线程需要初始化一个类,仅有一个线程会进⾏,其他线程需要等待。当活动的线程完成初始化之后, 它必须通知其他等待线程

所以这个被hang住的问题简单来说就是:线程1持有了锁(Lock.class对象),然后sleep了一会儿;与此同时线程2创建Danger对象,在创建对象的过程中先进行类初始化,在类初始化过程中又因为需要获取锁(Lock.class对象)而等待线程1释放(Lock.class对象);在线程1睡醒了之后,也去创建Danger对象,同样,因为Danger对象当前的状态是处于类初始化进行中的状态,线程1要等待正在初始化Danger类的那个线程要先把类初始化完成才能构造对象。于是造成了相互等待的境况。

在以前的分享的jvm内存管理的ppt里曾提到过这个例子,也可以参考一下这个ppt

同事周忱 曾用pstack看过到底hang在什么地方,参考他的gist,在ubuntu上pstack不太好用,可以直接使用jstack -m来看

----------------- 17643 -----------------
0x00007f897f250d84  __pthread_cond_wait + 0xc4
0x00007f897ea5ec85  _ZN13ObjectMonitor4waitElbP6Thread + 0x785
0x00007f897e865c1b  _ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread + 0x31b
0x00007f897e865f19  _ZN13instanceKlass10initializeEP6Thread + 0x49
0x00007f897e8999d4  _ZN18InterpreterRuntime4_newEP10JavaThreadP19constantPoolOopDesci + 0x214
0x00007f897501d98a  * Test$1.run() bci:24 line:28 (Interpreted frame)
0x00007f89750004f7  <StubRoutines>
0x00007f897e8a67bf  _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x54f
0x00007f897e8a5802  _ZN9JavaCalls12call_virtualEP9JavaValue11KlassHandleP6SymbolS4_P17JavaCallArgumentsP6Thread + 0x1c2
0x00007f897e8a585f  _ZN9JavaCalls12call_virtualEP9JavaValue6Handle11KlassHandleP6SymbolS5_P6Thread + 0x4f
0x00007f897e8dcfc6  _ZL12thread_entryP10JavaThreadP6Thread + 0x86
0x00007f897eb8bfc0  _ZN10JavaThread17thread_main_innerEv + 0xc0
0x00007f897ea69ec2  _ZL10java_startP6Thread + 0x132

----------------- 17634 -----------------
0x00007f897f2510fe  __pthread_cond_timedwait + 0x13e
0x00007f897ea5d004  _ZN13ObjectMonitor6EnterIEP6Thread + 0x294
0x00007f897ea5de87  _ZN13ObjectMonitor5enterEP6Thread + 0x297
0x00007f897e89ced9  _ZN18InterpreterRuntime12monitorenterEP10JavaThreadP15BasicObjectLock + 0x99
0x00007f897501e299  * Danger.<clinit>() bci:24 line:12 (Interpreted frame)
0x00007f89750004f7  <StubRoutines>
0x00007f897e8a67bf  _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x54f
0x00007f897e8a6055  _ZN9JavaCalls4callEP9JavaValue12methodHandleP17JavaCallArgumentsP6Thread + 0x25
0x00007f897e8658ac  _ZN13instanceKlass27call_class_initializer_implE19instanceKlassHandleP6Thread + 0xbc
0x00007f897e8658f5  _ZN13instanceKlass22call_class_initializerEP6Thread + 0x25
0x00007f897e865a9f  _ZN13instanceKlass15initialize_implE19instanceKlassHandleP6Thread + 0x19f
0x00007f897e865f19  _ZN13instanceKlass10initializeEP6Thread + 0x49
0x00007f897e8999d4  _ZN18InterpreterRuntime4_newEP10JavaThreadP19constantPoolOopDesci + 0x214
0x00007f897501d998  * Test.main(java.lang.String[]) bci:23 line:41 (Interpreted frame)
0x00007f89750004f7  <StubRoutines>
0x00007f897e8a67bf  _ZN9JavaCalls11call_helperEP9JavaValueP12methodHandleP17JavaCallArgumentsP6Thread + 0x54f
0x00007f897e8a6055  _ZN9JavaCalls4callEP9JavaValue12methodHandleP17JavaCallArgumentsP6Thread + 0x25
0x00007f897e8b27de  _ZL17jni_invoke_staticP7JNIEnv_P9JavaValueP8_jobject11JNICallTypeP10_jmethodIDP18JNI_ArgumentPusherP6Thread.isra.126.constprop.151 + 0x1ce
0x00007f897e8bae80  jni_CallStaticVoidMethod + 0x160
0x00007f897fa27f7b      ????????

效果不如pstack那么直观,但也可以发现instanceKlass::initialize_impl和ObjectSynchronizer::waitUninterruptibly方法,由此线索可再结合jvm代码进一步深究。

发表评论

电子邮件地址不会被公开。 必填项已用*标注