Java渴望单例创建线程安全吗?

时间:2018-10-07 11:20:46

标签: java multithreading singleton

我喜欢Java中渴望的单例的简单性,并且有关它的大多数文章都将其创建线程称为安全的。

class Singleton {

public static final Singleton instance = new Singleton ();

    private Singleton (){};

    public static Singleton getInstance(){

        return instance;

    }
}

但是,我听说有人声称它的创建可能毕竟不是线程安全的。例如,一个消息来源声称,如果使用多个类加载器或App域,那是不安全的。

JVM是否保证Eager Singleton的创建是线程安全的,例如,两个线程不会意外地同时创建Singleton?

编辑: 创建对象的线程安全时是否需要关键字final?如果该字段不是final,它不是线程吗?

3 个答案:

答案 0 :(得分:8)

您使用的方法是线程安全的。由于您尚未引用您正在谈论的声明,因此我无法直接解决它们。 但是Java语言规范对此主题很明确。

section 17.5中描述

  

final字段还允许程序员实现线程安全的不可变的   没有同步的对象。线程安全的不可变对象是   被所有线程视为不可变的,即使数据争用用于传递   引用线程之间的不可变对象。这可以提供   安全保证,防止因错误或不正确使用不可变类   恶意代码。 final字段必须正确使用才能提供   保证不变性。

     

当一个对象被认为是完全初始化的   构造函数完成。只能看到引用的线程   该对象已完全初始化后的对象   查看该对象最终的正确初始化的值   字段。

答案 1 :(得分:2)

我不认为“热心单身”这个名字是合理的。

考虑JLS §12.4.1., When Initialization Occurs

<块引用>

类或接口 T 将在以下任何一项第一次出现之前立即初始化:

  • T 是一个类,并创建了 T 的实例。
  • 调用了 static 声明的 T 方法。
  • 分配了 static 声明的 T 字段。
  • 使用了由 static 声明的 T 字段,并且该字段不是常量变量 (§4.12.4)。

因此,初始化以及 Singleton 的实例化将在第一次调用方法 getInstance() 时发生,或者,由于您创建了字段 public,当字段是第一次访问,以先到者为准。 但不是更早。换句话说,这个初始化已经和所有其他尝试执行延迟初始化的尝试一样懒惰。

此初始化的安全性由 JLS §12.4.2, Detailed Initialization Procedure

给出 <块引用>

对于每个类或接口C,都有一个唯一的初始化锁 LC。从 CLC 的映射由 Java 虚拟机实现。初始化 C 的过程如下 如下:

  1. LC 同步初始化锁 C。这涉及等到当前线程可以获取 LC
  2. 如果 ClassC 对象指示其他线程正在进行 C 的初始化,则释放 LC 并阻塞当前线程,直到通知正在进行的初始化已完成,此时重复此步骤。
  3. 如果 ClassC 对象指示当前线程正在对 C 进行初始化,那么这必须是一个递归的初始化请求。释放 LC 并正常完成。
  4. 如果 ClassC 对象指示 C 已经初始化,则不需要进一步的操作。释放 LC 并正常完成。

这是一个过程,对类引用的每次解析都遵循,获取类的唯一初始化锁,并在类已经初始化或当前线程是执行初始化的线程时释放它。这个锁保证了线程安全,不管字段是否声明了final,只要只在类初始化时写入即可。

为什么它仍然是实现单例的最有效方式的原因在同一章中给出:

<块引用>

当可以确定类的初始化已经完成时,实现可以通过省略步骤 1 中的锁获取(并在步骤 4/5 中释放)来优化此过程,前提是,就内存模型,所有在获取锁时存在的先发生后排序,在执行优化时仍然存在。

由于每个类只初始化一次,然后与初始化时间相比,在这个初始化状态下使用了很长时间,因此这种优化具有非常大的影响,因此,自第一个 Java 版本以来,这是最先进的。但是,正如这篇笔记所说,优化不能破坏线程安全

答案 2 :(得分:0)

即使我们从final中删除public static final Singleton instance = new Singleton ();,也是线程安全的。

这是因为JVM保证将在任何线程访问静态instance变量之前创建实例。