惰性初始化造成的死锁问题
下面我们来看看下面这段代码,我们看看它会打印什么东西出来。
我们看上面的代码开始定义一个静态的boolean initialized 其初始值为false。然后再定义一个线程,在他的run方法中我们将 initialized 设置为true,主线程启动了后台线程,然后有调用join()方法等待后台线程结束,当后台线程执行完后我们可以知道 initialized 被置为true,然后我们在主方法中输出 initialized
的值。但是我们的程序不会输出任何的东西,他应该是被挂起了。。。。
在解决这个问题我们需要知道一个初始化的细节。当一个线程访问一个类的某个成员的时候,它会去检查这个类是否已经被初始化。在一般情况下,我们有四种情况发生。
- 这个类尚未被初始化。
- 这个类正在被当前线程初始化:这是对初始化的递归请求。
- 这个类正在被其他线程而不是当前线程初始化。
- 这个类已经被初始化。
当主线程调用Lazy.main方法时,它会检查Lazy类是否已经被初始化。此时它并没有被初始化(情况1),所以主线程会记录下当前正在进行初始化,并开始对这个类进行初始化。按照我们前面的分析,主线程会将initialized的值设为false,创建并启动一个后台线程,该线程的run方法会将initialized设为true,然后主线程会等待后台线程执行完毕。此时,有趣的事情开始了。
那个后台线程调用了它的run方法。在该线程将Lazy.initialized设为true之前,它也会去检查Lazy类是否已经被初始化。这个时候,这个类正在被另外一个线程进行初始化(情况3)。在这种情况下,当前线程,也就是那个后台线程,会等待Class对象直到初始化完成。遗憾的是,那个正在进行初始化工作的线程,也就是主线程,正在等待着后台线程运行结束。因为这2个线程现在正相互等待着,该程序就死锁了(deadlock)。这就是所有的一切,真是遗憾。有2种方法可以订正这个程序。到目前为止,最好的方法就是不要在类进行初始化的时候启动任何后台线程:有些时候,2个线程并不比1个线程好。
总之,在类的初始化期间等待某个后台线程很可能会造成死锁。要让类初始化的动作序列尽可能地简单。类的自动初始化被公认为是语言设计上的难题,Java的设计者们在这个方面做得很不错。如果你写了一些复杂的类初始化代码,很多种情况下,你这是在搬起石头砸自己的脚。