Java:单例模式的七种写法


Java的单例模式在开发中常用到,下面记录一下Java:单例模式的七种写法。

文章转载至:http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

第一种(懒汉,线程不安全):

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static Singleton getInstance() {
        if(null!=instance){
            instance = new Singleton();
        }
        return instance;
    }
}

这使用的是一种懒加载方式,即当需要使用时才去判断选择创建;若此时同时有多个调用就会出现多次创建的问题;因此线程不安全

第二种(懒汉,线程安全):

public class Singleton {
    private static Singleton instance;
    private Singleton (){}
    public static synchronized Singleton getInstance() {
        if(null!=instance){
            instance = new Singleton();
        }
        return instance;
    }
}

这种通过加synchronized锁的方式虽然线程安全,但是效率非常的低。

第三种(饿汉):

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }
}

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到的懒加载效果。

第四种(饿汉,变种):

public class Singleton {
    private static Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }
}

这个和第三种差不多。

第五种(静态内部类):

public class Singleton {
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。

第六种(枚举):

public class Singleton {
    private Singleton(){}
    public static Singleton getInstance(){
        return SingletonEnum.INSTANCE.getInstance();
    }
    private static enum SingletonEnum{
        INSTANCE;
        private Singleton singleton;
        //JVM会保证此方法绝对只调用一次
        private SingletonEnum(){
            singleton = new Singleton();
        }
        public Singleton getInstance(){
            return singleton;
        }
    }
}

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但是我目前在实际开发中还没看到过这种写法。

第七种(双重校验锁):

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if (null == singleton) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

这个是第二种方式的升级版,俗称双重检查锁定。在JDK1.5之后,双重检查锁定才能够正常达到单例效果。

不过好像在高并发的情况下,可能会出现空对象的问题;即在new对象时,Java虚拟机完全有可能先new出来一个空的未调用过构造函数的instance对象,然后再将其赋值给instance引用,然后再调用构造函数,对instance对象当中的元素进行初始化。当还未初始化时正好有个线程在调用,此时会直接返回,而返回的对象还未被初始化。但是对于普通的一般不会出现此种问题。

不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。


特别提醒:扫码关注微信订阅号'起岸星辰',实时掌握IT业界技术资讯! 转载请保留原文中的链接!
  目录