【问题标题】:Java singleton class and multi-threadingJava单例类和多线程
【发布时间】:2019-02-28 19:18:49
【问题描述】:

伙计们- 我有一个助手类,其任务是根据参数构建一些消息。该类本身没有任何私有数据(当然实例除外)。

public class RequestBuilder {
    private static RequestBuilder instance = new RequestBuilder();

    private RequestBuilder() {}

    public static RequestBuilder getInstance() {
        return instance;
    }

    public SetRequest buildSetRequest(Path prefix,
                                      Path path,
                                      ConfigEntity configEntity,
                                      Any protoAnyData) {
        .....
        .....
        return setRequest;
    }

    public GetRequest buildGetRequest(Path prefix,
                                      Path Path,
                                      RetrieveRequestEntity retrieveRequestEntity,
                                      Encoding encoding) {
        .....
        .....
        return getRequest;
    }
}

我了解单例类对多线程不友好。在这种情况下,当 2 个线程尝试同时执行 buildSetRequest() 会发生什么?

感谢您的宝贵时间。

编辑: 根据我的需要,并且正如@BoristheSpide 在下面的 cmets 中所建议的那样,我将把这个类作为一个实用程序类,并进行以下更改: 1. 最终确定。 2. 使方法静态化。 3. 删除所有单例引用。

public final class RequestBuilder {

    private RequestBuilder() {}

    public static SetRequest buildSetRequest(Path prefix,
                                      Path path,
                                      ConfigEntity configEntity,
                                      Any protoAnyData) {
        .....
        .....
        return setRequest;
    }

    public static GetRequest buildGetRequest(Path prefix,
                                      Path Path,
                                      RetrieveRequestEntity retrieveRequestEntity,
                                      Encoding encoding) {
        .....
        .....
        return getRequest;
    }
}

我将保留原始代码,因为它仍然有效,并为 cmets 提供上下文和此问题的答案。

【问题讨论】:

  • 如果没有共享状态——即没有字段——那就没问题了。
  • ...虽然这确实引出了一个问题,即为什么你需要一个单身人士。只需将其设为实用程序类即可。
  • 那么buildSetRequest 到底是什么?如果该调用修改了configEntity 参数;并且两个调用线程使用相同的ConfigEntity 实例。那么你可能会遇到问题(因为你有共享可变状态)
  • @Daniele,不,没有任何输入被修改。

标签: java multithreading singleton


【解决方案1】:

在这种情况下,不要太多,因为您的构造函数是空的(其他人提到的没有共享状态)。当您有多个必须初始化的私有实例变量时,问题就开始了。在这种情况下,您需要一些保护,例如 doble-check:

private static volatile RequestBuilder instance;

private RequestBuilder() {}

public static RequestBuilder getInstance() {
    if (instance == null) {
        synchronized (RequestBuilder.class) {
            if (instance == null) {
                instance = new RequestBuilder();
            }
        }
    }
    return instance;
}

原因是线程可以随时挂起。如果当前构造实例的线程被挂起,而另一个线程来了,可能有一半初始化的实例变量,对象可能最终处于损坏状态。

编辑:关于buildSetRequest()

代码在一个方法里面,如果这个方法自己创建自己的实例或者与线程安全的类一起工作,不会有任何问题。

【讨论】:

  • 这只是惰性初始化和急切初始化的区别。请阅读有关类初始化程序的规则 - 从构造的角度来看,OP 的代码是完全安全的。
  • @BoristheSpider 感谢您的反对票。是的,但是 OP 将单例类的事实与对buildSetRequest() 的调用混为一谈,我不知道如何集中答案。感谢您的反馈,并随时扩展我的解释。
  • 对不起,这不是您回答的重点。这是关于您答案的整个第一部分都不正确。鉴于 Java 语言规范,半初始化实例不可能泄漏。您的建议为关于并发的虚假“事实”增加了虚假的复杂性。
  • @BoristheSpider 不管怎样,但我回答的是正确的方法。检查一下,您会发现,Java 内存模型有效地允许发布部分初始化的对象:https://*.com/questions/45857765/partial-constructed-objects-in-the-java-memory-model。同样,您也可以随时回答 OP 的问题。
  • 这是一个完全不同的例子。您建议 class initialiser 不受 JVM 保护。构造函数调用受类初始化程序的保护。我不知道还能说什么——这个答案从根本上说是不正确的,并且传播了关于 Java 工作原理的危险错误信息。我要求您作为专业人士删除它。