java单例实例化
下图是对singleton对象的创建
public class Map_en_US extends mapTree {
private static Map_en_US m_instance;
private Map_en_US() {}
static{
m_instance = new Map_en_US();
m_instance.init();
}
public static Map_en_US getInstance(){
return m_instance;
}
@Override
protected void init() {
//some code;
}
}
我的问题是使用静态块进行实例化的原因是什么。我熟悉下面的单例实例化形式
public static Map_en_US getInstance(){
if(m_instance==null)
m_instance = new Map_en_US();
}
# 1 楼答案
如果在
getInstance()
方法中初始化,则可以获得竞速条件,即如果两个线程同时执行if(m_instance == null)
检查,则两个线程都可能看到实例为null,因此都可能调用m_instance = new Map_en_US();
由于静态初始值设定项块只执行一次(由执行类加载器的一个线程执行),因此没有问题
Here's a good overview.
# 2 楼答案
这种消除静态块的方法怎么样:
它做同样的事情,但更整洁
此语法的解释:
外部大括号集创建一个匿名类
内部的一组支架称为“实例块”——它在构造过程中激发
这种语法通常被错误地称为“双大括号初始值设定项”语法,通常是那些不了解情况的人使用的
另外,请注意:
m_
是实例(ie成员)字段的命名约定前缀s_
是类(即静态)字段的命名约定前缀因此,我将字段的名称更改为
s_...
# 3 楼答案
thread-safe
,因为除了返回实例之外,您在该方法中不做任何事情李># 4 楼答案
静态块在JVM首次加载类时执行。正如Bruno所说,这有助于线程安全,因为两个线程不可能第一次为同一个getInstance()调用发生冲突
# 5 楼答案
原因是线程安全性
您所熟悉的表单可能会多次初始化单例。此外,即使在多次初始化之后,不同线程对
getInstance()
的未来调用也可能返回不同的实例!另外,一个线程可能会看到一个部分初始化的单例实例!(假设构造函数连接到DB并进行身份验证;一个线程可能能够在身份验证发生之前获得对单例的引用,即使它是在构造函数中完成的!)处理线程时存在一些困难:
并发性:它们必须具有并发执行的潜力
可见性:一个线程对内存的修改可能对其他线程不可见
重新排序:无法预测代码的执行顺序,这可能会导致非常奇怪的结果
您应该研究这些困难,以准确理解为什么这些奇怪的行为在JVM中是完全合法的,为什么它们实际上是好的,以及如何保护它们
JVM保证静态块只执行一次(除非您使用不同的
ClassLoader
加载和初始化类,但是细节超出了这个问题的范围,我想说),并且只由一个线程执行,并且保证每个其他线程都可以看到它的结果这就是为什么应该在静态块上初始化单例
我喜欢的模式:线程安全和懒惰
上面的模式将在执行第一次看到对类
Map_en_US
的引用时实例化单例(实际上,只有对类本身的引用将加载它,但可能尚未初始化它;有关更多详细信息,请检查引用)。也许你不想那样。也许您希望只在第一次调用Map_en_US.getInstance()
时初始化单例(正如您所说的熟悉的模式所做的那样)如果这是您想要的,您可以使用以下模式:
在上面的代码中,只有在初始化类
SingletonHolder
时,才会实例化单例。这只会发生一次(除非,如我前面所说,您使用的是多个类加载器),代码将只由一个线程执行,结果将不会出现可见性问题,并且初始化将只在对SingletonHolder
的第一次引用时发生,这发生在getInstance()
方法内部。这是我在需要单身时最常使用的模式另一种模式
一,
synchronized getInstace()
正如对此答案的评论中所讨论的,还有另一种以线程安全的方式实现singleton的方法,这与您熟悉的(坏的)方法几乎相同:
上面的代码由内存模型保证是线程安全的。JVM规范声明了以下内容(以一种更神秘的方式):设L为任何对象的锁,设T1和T2为两个线程。T1释放L发生在T2获得L之前
这意味着,T1在释放锁之前所做的每一件事,在获得相同的锁之后,其他线程都可以看到
因此,假设T1是进入
getInstance()
方法的第一个线程。在完成之前,没有其他线程能够进入相同的方法(因为它是同步的)。它将看到instance
为空,将实例化一个Singleton
并将其存储在字段中。然后它将释放锁并返回实例然后,等待锁的T2将能够获取锁并输入方法。由于它获得了与T1刚刚释放的锁相同的锁,T2将看到字段
instance
包含与T1创建的Singleton完全相同的实例,并且我会把它还给你。更重要的是,由T1完成的单例初始化发生在T1释放锁之前,发生在T2获取锁之前,因此T2无法看到部分初始化的单例上面的代码完全正确。唯一的问题是对单例的访问将被序列化。如果这种情况经常发生,将会降低应用程序的可伸缩性。这就是为什么我更喜欢上面展示的
SingletonHolder
模式:对单例的访问将是真正并发的,而不需要同步二,。双重检查锁定(DCL)
通常,人们担心锁的获取成本。我已经读到,现在它与大多数应用程序并不相关。锁获取的真正问题是,它通过序列化对同步块的访问而损害了可伸缩性
有人发明了一种巧妙的方法来避免获得锁,这种方法被称为双重检查锁。问题是,大多数实现都被破坏了。也就是说,大多数实现都不是线程安全的(即,与原始问题上的
getInstace()
方法一样是线程不安全的)实施DCL的正确方法如下:
此正确实现与不正确实现之间的区别在于
volatile
关键字为了理解为什么,让T1和T2是两个线程。首先假设该字段不是易失性的
T1进入
getInstace()
方法。它是第一个输入它的字段,因此该字段为空。然后进入同步块,然后进入第二个if。它的计算结果也为true,因此T1创建一个新的单例实例并将其存储在字段中。然后释放锁,并返回单例。对于这个线程,可以保证Singleton被完全初始化现在,T2进入
getInstace()
方法。它有可能(尽管不能保证)看到instance != null
。然后它将跳过if
块(因此它将永远不会获得锁),并将直接返回单例实例。由于重新排序,T2可能无法在其构造函数中看到单例执行的所有初始化!再次查看db连接单例,T2可能会看到一个已连接但尚未验证的单例有关更多信息
。。。我推荐一本精彩的书,Java并发实践,还有Java语言规范
# 6 楼答案
这取决于init方法的资源密集程度。例如,如果它做了很多工作,那么您可能希望在应用程序启动时完成这些工作,而不是在第一次调用时完成。也许它可以从网上下载地图?我不知道