对于单例模式的“懒汉式”和“饿汉式”大家都比较熟悉了,先简单说一下
饿汉式就比较简单了,今天的重点是后面的懒汉式:
class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
懒汉式就是延迟加载,但一般的形式会出现线程安全问题,所以出现了以下的改良版:通过加锁保证线程安全,双重判断提高效率
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
以上就是我们通常见到的单例模式,但是昨天看到了一篇博客,今天将主要内容跟大家分享一下:
java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码中的:s = new Singleton(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给 s 这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,s 已经被设置为非空了。
也就是说如果有一个A线程在调用构造方法之前将 s 变量置为非空了,正好在此时停了下来,线程B过来判断 s 为非空,则将 s 返回了,但返回的却是无效的引用,所以导致错误。
简便的解决方法就是使用内部类的形式:
public class Singleton {
//内部类
private static class SingletonHolder{
//单例变量
private static Singleton instance = new Singleton();
}
//私有化的构造方法,保证外部的类不能通过构造器来实例化。
private Singleton() {}
//获取单例对象实例
public static Singleton getInstance() {
System.out.println("我是内部类单例!");
return SingletonHolder.instance;
}
}
因为内部类也是只有在调用时才会加载,所以同样实现了延迟加载的功效。
下面是原博客的地址:http://blog.csdn.net/xu_song/article/details/9142383
另外还有一点是我自己总结的一点关于单例的小知识:
大家都知道单例有“懒汉式”和“饿汉式”,其实张老师提到过单例还有一种实现方式,那就是“枚举”。
因为枚举也是不能用 new 来创建对象的,所以当枚举中只定义了一个对象值时,就成了特殊的单例。
这里我想说的一点就是,以前看过论坛里有说过,通过暴力反射可以获得单例的构造函数,然后再通过构造函数就可以创建对象,这样就破坏了单例的对象唯一性。
但是如果使用枚举来实现单例的话,我自己实验了一下,通过暴力反射并没有成功的创建对象,也许是我做的有疏漏,有兴趣的同学可以自己来试验一下,并分享一下结果。
|
|