在今年遭遇过一次执行了某个操作后,代码中某一个执行流就是不能执行下去的故障现象......,像极了线程死锁!
好在,知道Java提供了些工具可以观察JVM中所有线程的快照,例如在Linux使用kill -3 pid让JVM输出各线程的堆栈快照,可以去分析、分析是否有死锁?
但,初步分析结果是令人惊讶的!
Java程序输出的dump信息显示没有明显的死锁,但是在事后来看,事后诸葛亮一下,日志也是给出了一些蛛丝马迹。
dump信息中显示其中有一个特殊线程,虽然线程状态处于runnable状态,但是线程执行流dump日志中有一个细节的特殊指明--Object.wait()状态。
当时一直疑惑,线程状态处于runnable状态就应该执行流继续执行,但是,在实际故障现象中就是这个执行流无法执行下去!苦恼了很久。。。
后来详细分析各线程堆栈快照,以及查看Object.wait所需要等待的物事,发现此无法继续执行线程等待是一个在另外一个执行流中处于类加载的Class。进一步深入分析dump信息,又显示此Class类加载时竟然要获取一个业务级别的Lock,而这个Lock在执行不下去执行流,已在使用这个类前,已先行掌握到!
虽然在这个场景下,没有出现典型的死锁--多于两个执行流的线程在申请锁的顺序上存在不一致,这样就会在多线程竞争情况下就容易出现死锁。但考虑到类加载,JVM会保证Class只会被加载一次且保证线程安全,推想来看这里面肯定是存在一个隐含的锁机制。
因为类加载作为一种含有特殊的隐含锁过程,执行流必须等待类加载完成,如果此类加载过程中,又去获取其它执行流可能先期占用的业务级别上的锁,则就容易在此类加载时出现类似隐性死锁现象。JAVA世界中又一死锁的可能场景!
后来解决起来也很简单,类加载时申请业务级别的锁,这种代码实现在我看来就是一种设计瑕疵,在不必要底层机制过程中反调、回调属于代码运行时中高层资源,存在设计上回路,应该避免在程序金字塔中出现飞线!
#附录 线程执行流
Thread1 Thread2
hold Xlock ...
load Yclass load Yclass
Object.wait Yclass.init #类加载已展开
apply for Xlock