你会写哪一种单例模式?

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。

功能

单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。

饿汉法

顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下:

    public class Singleton { 
        private static Singleton = new Singleton();
        private Singleton() {}
        public static getSignleton(){
            return singleton;
        }
    }

饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。但是无法做到延迟创建对象,从而减小负载。

懒汉式

单线程写法

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

这种不加同步的懒汉式写法虽然简单但同时它是线程不安全的,这种写法由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。

线程安全的写法

如何实现懒汉式的线程安全?加上synchronized?

public static synchronized Singleton getInstance(){}

但这样会降低整个访问的速度,而且每次都要判断。现在比较流行的是用双重检查加锁。

双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。

双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

    /**
     * 双重检查加锁的单例模式
     * @author dream
     *
     */
    public class Singleton {
    
        /**
         * 对保存实例的变量添加volitile的修饰
         */
        private volatile static Singleton instance = null;
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            //先检查实例是否存在,如果不存在才进入下面的同步块
            if(instance == null){
                //同步块,线程安全的创建实例
                synchronized (Singleton.class) {
                    //再次检查实例是否存在,如果不存在才真正的创建实例
                    instance = new Singleton();
                }
            }
            return instance;
        }
        
    }

但是双重加锁机制,一句话说是“成也volatile,败也volatile”,因为volatile关键字在JDK1.5之前无法保证线程安全,所有双重加锁机制也不是完美的,也是有瑕疵的。

一种更好的单例实现方式

静态内部类法,它可以延时加载,并且能保证线程安全,我们把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。

    public class Singleton {
    
        /**
         * The class-level inner class, that is, the member-like inner class of the static class, the instance of
                 * the inner class has no binding relationship with the instance of the outer class, and it will be loaded                  
                 * only when it is called, thus realizing lazy loading .
         * @author dream
         */
        private static class SingletonHolder{
            /**
             * Static initializer, thread safety is guaranteed by JVM.
             */
            private static Singleton instance = new Singleton();
        }
        
        /**
         * Privatization construction method.
         */
        private Singleton(){
            
        }
        
        public static Singleton getInstance(){
            return SingletonHolder.instance;
        }
    }

优缺点

  • 懒汉式是典型的时间换空间
  • 饿汉式是典型的空间换时间

何时选用单例模式

当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是jdk版本)下,自然有不同的最优解(或者说较优解),比如双重检查锁法,不能在jdk1.5之前使用,但是本人是Android开发者,而Android基本都是基于JDK1.6以上开发的,所以我个人偏爱比较双重检查锁法。
当然,还有一种更加优雅的写法:枚举写法,但为什么我没有介绍呢,应为它在Android平台上却是不被推荐的。所以感兴趣的同学可以自行去了解一下。

你可能感兴趣的:(你会写哪一种单例模式?)