zoukankan      html  css  js  c++  java
  • 谜题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,然后主线程会等待后台线程执行完毕。此时,有趣的事情开始了。
    那个后台线程调用了它的 run 方法。在该线程将 Lazy.initialized 设为 true
    之前,它也会去检查 Lazy 类是否已经被初始化。这个时候,这个类正在被另外
    一个线程进行初始化(情况 3)。在这种情况下,当前线程,也就是那个后台线
    程,会等待 Class 对象直到初始化完成。遗憾的是,那个正在进行初始化工作的
    线程,也就是主线程,正在等待着后台线程运行结束。因为这 2 个线程现在正相
    互等待着,该程序就死锁了(deadlock)。这就是所有的一切,真是遗憾。有 2
    种方法可以订正这个程序。到目前为止,最好的方法就是不要在类进行初始化的
    时候启动任何后台线程:有些时候,2 个线程并不比 1 个线程好。更一般的讲,
    要让类的初始化尽可能地简单。订正这个程序的第 2 种方法就是让主线程在等待
    后台线程之前就完成类的初始化:

    // Bad way to eliminate the deadlock. Complex and error prone
    public 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
    的设计者们在这个方面做得很不错。如果你写了一些复杂的类初始化代码,很多
    种情况下,你这是在搬起石头砸自己的脚。

  • 相关阅读:
    javascript 写一个随机范围整数的思路
    Promise中的next 另一个用法
    继上一篇随笔,优化3张以上图片轮播React组件
    低性能3张图片轮播React组件
    用函数式编程思维解析anagrams函数
    Python time time()方法
    torch.view().expand()
    pytorch中的top_K()函数
    设定学习率衰减
    两个集合求交集
  • 原文地址:https://www.cnblogs.com/yuyu666/p/9842456.html
Copyright © 2011-2022 走看看