【问题标题】:Is initialization-on-demand holder idiom still safe if parameterized?如果参数化,按需初始化持有者习语是否仍然安全?
【发布时间】:2018-09-08 12:34:45
【问题描述】:

如果我这样修改the stock initialization-on-demand holder idiom example

public class Something {

    //internal params, unchangeable after the initialization
    private final List<String> params;

    //changeable params that will be used for initialization
    private final static List<String> initParams = new ArrayList<>();

    private Something(List<String> params) {
        this.params = params;
    }

    //allow params to be added prior to initialization
    public static void addParam(String param) {
        initParams.add(param);
    }

    private static class LazyHolder {
        static final Something INSTANCE =
            new Something(Collections.unmodifiableList(initParams));
    }

    public static Something getInstance() {
        return LazyHolder.INSTANCE;
    }

    public int calculate() {
       return params.size(); //use the internal params somehow
    }
}

并按如下方式使用:

  • 只有1个线程多次调用addParam准备初始化(唯一的线程)
  • 许多线程(可能在写入线程之前创建)后来调用getInstance 来获取单例实例(许多读取线程)
  • getInstance 的初始调用可以由writing 线程或reading 线程之一执行

这种用法是否安全(尽管很复杂)?
是否会根据哪个线程(读取器或写入器)首先调用 getInstance 而发生变化?
如果静态的initParams 变量是volatile?

如果第一次调用是由reading线程(在writing线程之前创建的)执行的,它的initParams内部视图是否会过时并导致初始化实例也会过时?

【问题讨论】:

    标签: java multithreading concurrency thread-safety singleton


    【解决方案1】:

    这种用法安全吗(尽管很复杂)?

    不,我不认为你有任何保证,在当前所述的问题中,第一次调用 getInstance 将发生在最后一次调用 addParam 之后。

    您确实说对getInstance 的第一次调用将在“稍后”发生,但是您随后通过说“[阅读器线程]可能已在编写线程之前创建”来限定这一点,这让我认为您无法保证此订单。

    如果您可以保证 init 和读取之间的这种“发生后”关系,那么我认为这是线程安全的,是的,原因与更简单的版本相同。

    有什么变化取决于哪个线程(读取器或写入器)首先调用 getInstance?

    是的,如果编写线程是第一个调用getInstance,那么事情应该是线程安全的。这是因为它对全局变量 initParams 的写入保证会发生在稍后在同一线程上读取之前。

    但是,这与我上面讨论的基本相同,所以我认为这只是在移动。

    如果将静态 initParams 变量设置为 volatile,会有什么变化吗?

    这将有助于缩小第一次调用 getInstance 和最后一次调用 addParam 之间的竞争,但我认为您仍然依赖于后者发生在前者“之后”。

    退后一步

    这种设计是脆弱、复杂和丑陋的。您正在使用全局变量“initParams”并依赖于一些非常棘手的副作用和排序。

    您说您有一个“编写器线程”,它构建了“Something”实例,并且“稍后”它将被读取。

    你能不重新安排事情,让所有的写作都严格在阅读开始之前发生,并完全停止使用静态变量吗?

    【讨论】:

    • 我稍微修改了这个问题,以更好地突出我的疑虑。请看粗体部分。谢谢!
    • 谢谢。我已经更新了回应。我认为这个问题已经被抽象/匿名到答案不是很有趣的地步。这似乎不是一件好事。
    • 太好了,谢谢!我当然同意这种设计非常疯狂,并且永远不会真正使用它。我只是以某种方式开始讨论在这个理论案例中会发生什么,并且真的想在我的大脑中解开它。这就是为什么它是抽象的,它不是我的实际案例,只是一个思想实验:)
    【解决方案2】:

    您的示例不是线程安全的。由于您引入了对不受线程安全类加载保护的共享变量的写入,因此您失去了订购前发生的事情。

    场景是

    Thread 1: Write Thread
       read initParams
       initParams.add(param)
    
    Thread 2: First read thread
       synchronized(class_loader_lock){ 
          load class
          read initParams
       }
    

    线程 1 的写入,由于不与类加载同步,因此可以读取陈旧。

    【讨论】:

    • 我稍微修改了这个问题,以更好地突出我的疑虑。请看粗体部分。谢谢!
    猜你喜欢
    • 2016-06-03
    • 2014-11-25
    • 2014-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-06
    • 1970-01-01
    • 2016-07-18
    相关资源
    最近更新 更多