java谜题--类初始化时容易死锁情况
来源:互联网 发布:淘宝网怎么退出智能版 编辑:程序博客网 时间:2024/06/04 20:39
谜题85:惰性初始化
下面这个可怜的小类实在是太懒了,甚至于都不愿意用通常的方法进行初始化,所以它求助于后台线程。这个程序会打印什么呢?每次你运行它的时候都会打印出相同的东西吗?public class Lazy { private static boolean initialized = false; static { Thread t = new Thread(new Runnable() { public void run() { initialized = true; } }); t.start(); try{ t.join(); }catch (InterruptedException e){ throw new AssertionError(e); } } public static void main(String[] args){ System.out.println(initialized); }}
虽然有点奇怪,但是这个程序看起来很直观的。静态域initialized初始时被设为false。然后主线程创建了一个后台线程,该线程的run方法将initialized的值设为true。主线程启动了后台线程之后,就调用了join方法等待它的结束。当后台线程完成运行的时候,毫无疑问initialized的值已经被设为了true。当且仅当这个时候,调用了main方法的主线程会打印出initialized的值。如果是这样的话,程序肯定会打印出true吗?如果你运行该程序,你会发现它不会打印任何东西,它只是被挂起了。
为了理解这个程序的行为,我们需要模拟它初始化的细节。当一个线程访问一个类的某个成员的时候,它会去检查这个类是否已经被初始化。在忽略严重错误的情况下,有4种可能的情况[JLS 12.4.2]:
- 这个类尚未被初始化。
- 这个类正在被当前线程初始化:这是对初始化的递归请求。
- 这个类正在被其他线程而不是当前线程初始化。
- 这个类已经被初始化。
当主线程调用Lazy.main方法时,它会检查Lazy类是否已经被初始化。此时它并没有被初始化(情况1),所以主线程会记录下当前正在进行初始化,并开始对这个类进行初始化。按照我们前面的分析,主线程会将initialized的值设为false,创建并启动一个后台线程,该线程的run方法会将initialized设为true,然后主线程等待后台线程执行完毕。此时,有趣的事情开始了。
PS:t.start() 方法调用run(),并不是马上运行完,需要等待主线程完成初始化以后,再运行设为true
那个后台线程调用了它的run方法。在该线程将Lazy.initialized设为true之前,它也会去检查Lazy类是否已经被初始化。这个时候,这个类正在被另外一个线程进行初始化(情况3)。在这种情况下,当前线程,也就是那个后台线程,会等待Class对象直到初始化完成。遗憾的是,那个正在进行初始化工作的线程,也就是主线程,正在等待着后台线程运行结束。因为这2个线程现在正相互等待着,该程序就死锁了(deadlock)。这就是所有的一切,真是遗憾。有2种方法可以订正这个程序。到目前为止,最好的方法就是不要在类进行初始化的时候启动任何后台线程:有些时候,2个线程并不比1个线程好。更一般的讲,要让类的初始化尽可能地简单。订正这个程序的第2种方法就是让主线程在等待后台线程之前就完成类的初始化:
// Bad way to eliminate the deadlock. Complex and error pronepublic class Lazy { private static boolean initialized = false; private static Thread t = new Thread(new Runnable() { public void run() { initialized = true; } }); static { t.start(); } public static void main(String[] args){ try{ t.join(); }catch (InterruptedException e){ throw new AssertionError(e); } System.out.println(initialized); }}
虽然这么做确实消除了死锁,但是它却是一个非常不好的想法。主线程需要等待后台线程完成工作,但是其他的线程不需要这么做。一旦主线程完成了对Lazy类的初始化,其他线程就可以使用这个类了。这使得在initialized的值还是false的时候,其他线程就可以观察到它。
总之,在类的初始化期间等待某个后台线程很可能会造成死锁。要让类初始化的动作序列尽可能地简单。类的自动初始化被公认为是语言设计上的难题,Java的设计者们在这个方面做得很不错。如果你写了一些复杂的类初始化代码,很多种情况下,你这是在搬起石头砸自己的脚。
- java谜题--类初始化时容易死锁情况
- Java多线程死锁情况
- Java多线程死锁情况模拟
- 死锁情况
- 死锁-嵌套容易发生死锁
- {Java}一个有关类属性初始化的有趣儿情况
- Java 类会执行初始化的5种情况
- java继承情况下初始化顺序
- java多线程中死锁情况的一个示例
- java多线程中死锁情况的一个示例
- [java]一个多线程引起的死锁(class初始化,锁)
- 【Java基础知识】Java虚拟机需立即初始化类的5种情况
- JAVA——JAVA类属性初始化的有趣儿情况
- Oracle 死锁情况总结
- oracle出现死锁情况
- 死锁发生的情况
- java编程思想读书笔记九(Final 关键字的用法&初始化与类的装载&继承情况下的初始化)
- java中应该尽量使用notifyall而不是notify(容易发生死锁)
- android环境搭建2
- 从DCL问题出发认识并发环境下的Java内存访问
- Bootloader代码分析
- 个人安卓学习笔记---搭建Androd开发环境
- linux服务器添加yum源
- java谜题--类初始化时容易死锁情况
- C#读取配置文件详解:转自(http://hi.baidu.com/huahua382811/blog/item/27d43cef3bd587f7b3fb956d.html)
- pywintypes25.dll
- 设计模式-工厂模式
- js使用数组
- vb中byref参数类型不符
- android中colors.xml中用到的主要颜色的代码。
- 开启Sencha Touch 之旅
- c++作业,,12.5