Java单例模式实现方式

实现方式一览

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

    public class Singleton {  
        private static Singleton instance;
    
        private Singleton (){}
    
        public static Singleton getInstance() {  
            if (instance == null) {  
                instance = new Singleton();  
            }  
            return instance;  
        }  
    }
    
  2. 第二种(懒汉,线程安全)

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

    这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。

  3. 第三种(懒汉,双重校验锁)

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

    在JDK1.5之后,双重检查锁定才能够正常达到单例效果。因为1.5之前的volatile关键字没有正确的实现其语义(存在乱序写入问题,即编译器重排优化,参考:Java单例模式中双重检查锁的问题)。

  4. 饿汉模式Ⅰ(静态属性直接初始化)

    public class Singleton {
    
        private static Singleton instance = new Singleton();
    
        private Singleton (){}
    
        public static Singleton getInstance() {  
            return instance;  
        }  
    }
    
  5. 饿汉模式Ⅱ静态属性静态块中初始化)

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

    4和5中的本质是一样的,基于classloder机制避免了多线程的同步问题。一般认为静态属性直接赋值和static块在类加载后就会执行,这是错误的认识,而是在类被主动使用时才会执行(初始化)。详见http://blog.csdn.net/berber78/article/details/46472789

  6. 静态内部类

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

    此模式与4和5保证线程安全的原理一样,只不过加了懒加载功能,因为私有静态内部类截断了其他触发初始化操作的条件,只剩下一个条件:访问SingletonHolder类的静态属性INSTANCE,也就是我们的单例对象。

  7. 枚举

    public enum Singleton {  
        INSTANCE;  
        public void whateverMethod() {
    
        }  
    }
    

    “这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象”
    上面是网上的人崇拜大神说的,其实他不明白其中的原理,上面的代码等价于:

     public class Singleton extends Enum<Singleton> {
    
        public static final Singleton[] values() {
            return (Color[])$VALUES.clone(); 
        }
    
        public static Singleton valueOf(String name) {
    
            //return $VALUES[indexOfname];大概逻辑
        }
    
        private Singleton(String s, int i) {
            super(s, i);
        }
    
        public static final Singleton INSTANCE;
    
    
        private static final Color $VALUES[];
    
        static {
            INSTANCE = new Color("INSTANCE", INSTANCE);
            $VALUES = (new Color[] { INSTANCE});
        }
    }
    

    自习分析,方向跟4和5有异曲同工之妙。但是在您不想使用INSTANCE时千万不要调用valueOf和values方法,因为您的意图不是使用INSTANCE,而此时却加载了实例。请不要在枚举类里面写静态方法和其他静态属性,因为你一旦访问,就会创建实例,违背了懒加载的初衷。而enum被推崇的另外一个原因是反序列化不会创建新对象,因为Enum类里的三个方法:

    /**
     * prevent default deserialization
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }
    
    private void readObjectNoData() throws ObjectStreamException {
        throw new InvalidObjectException("can't deserialize enum");
    }
    

    不是两个吗?你骗人!还有一个readResolve隐式实现了,readObject和readObjectNoData都直接抛出了异常,序列化机制明白是要阻止默认的反序列化方式,就会调用readResolve,readResolve返回一个匹配的对象就行了,我想java内部对于enum类型的这个方法的实现应该是直接返回INSTANCE吧!
    Effective Java作者Josh Bloch不会没发现这些问题吧?他应该发现了,只不过大神都懒得解释,因为对于规范使用枚举的人,一般不会出现那些问题。Josh Bloch应该是从简洁、不易出错、高性能的角度认为这是最好的方式。但是这种方式比较诡异,感觉枚举被用坏了似的。

总结

方式1单线程可用,但是单线程的程序很少,不能实现线程安全不推荐使用
方式2多线程可用,但是性能不好,不是每次访问都需要加上锁
方式3多线程可用,java 1.5版本之后可用
方式4和5多线程可用,但是不会懒加载
方式6多线程可用,very good
方式7简洁诡异,但是需要注意那几个点

你可能感兴趣的:(Java)