独一无二的单例模式

经典单例模式

单例模式被定义为:确保一个类只有一个实例,并提供一个全局访问点。

单例模式使得一个类只能有唯一的一个实例,这意味着不能再使用new关键字创建对象,因为一旦可以被new,就可以被多次new。

问题1:如何保证一个类只有一个实例?
问题2:不能使用new如何获取对象?

这两个问题的答案非常简单:

  1. 由于new对象会调用构造方法,所以只需要将构造方法定义为private(虽然很少这么干,但单例模式必须如此);这样创建对象的任务只能在类中完成;
  2. 定义明确指出需要提供一个全局访问点,意思就是需要拿到类中定义的对象,可以使用static方法,以下是经典单例模式的实现
    public class Singleton {
        private static Singleton singleton;
        private Singleton() {}
        
        public static Singleton getInstance() {
            if(singleton==null) {
                singleton=new Singleton();
            }
            return singleton;
        }
    }

单例模式没有复杂的类图结构,而且代码非常简单,但仍然需要注意一些细节:

  1. 构造方法私有化,只有类中才可以调用构造方法
  2. 全局访问点被定义为static方法,直接通过类名就可以调用
  3. 在getInstance()中,if(singleton==null)和静态成员singleton配合组成了单例模式的核心,只有第一次调用getInstance()时,其中的代码会全部执行。singleton只会被初始化一次。
  4. 如果不需要Singleton的实例,永远不会创建它的实例,这称为延迟实例化(lazy instance)

单例模式的类图如下:
单例UML
确实,目前为止单例模式非常简单;可是能够完全满足单例的需求吗?还需要看一看在多线程下的单例模式。

多线程下的单例模式

单例模式在多线程下可能会创建多个对象而违反单例模式的原则
多线程下的单例模式的问题

如图所示,如下线程A进入if语句但还未创建对象是,因为某些原因导致A发生了阻塞,由于A还没有来得及创建对象,所以线程B又进入了if语句,创建了一个实例,B执行完成后,A继续执行,虽然此时以及singleton已经不为空,但是A依然会创建对象,此时就破坏了单例模式的原则。

为了保证在多线程环境下能够正确的使用单例模式,可以采用三种方法:

  1. 同步方法
  2. “急切”创建实例
  3. 双重加锁

同步方法

不需要做任何改变,只需要在getInstance()方法前假设synchronized关键字

    public class Singleton {
        private static Singleton singleton;
        private Singleton() {}
        * 同步方法实现多线程下的单例模式
        * @return
        */
        public static synchronized Singleton getInstance() {
            if(singleton==null) {
                singleton=new Singleton();
            }
            return singleton;
        }
    }

同步方法非常简单粗暴,带来的问题也很明显:降低性能,每一次调用全局访问点,都会是一种累赘。

‘急切’创建实例

急切创建实例的意思是在类加载静态singleton时就创建出实例,这样无论如何都不可能再有第二个实例产生,但这样就失去了延迟实例化的好处。

    public class Singleton {
        //急切创建实例
        private static Singleton singleton=new Singleton();
        private Singleton() {}	
        /**
        * 急切创建实例实现多线程下的单例模式
        */
        public static Singleton getInstance() {
            return singleton;
        }
    }

这种方法在JVM加载类时即创建出实例,同时也简化了getInstance()方法

双重加锁

双重加锁也是采用同步的方法,但是会减少同步的使用以提高性能。

    public class Singleton {
        private volatile static Singleton singleton;
        private Singleton() {}
        /**
        * 双重锁实现多线程下的单例模式
        * @return
        */
        public static Singleton getInstance() {
            if(singleton==null) {
                synchronized (Singleton.class) {
                    if(singleton==null) {
                        singleton=new Singleton();
                    }
                }
            }
            return singleton;
        }
    }

同步代码块写在if中,意思是如果singleton没有实例化才进行同步;
volatile关键字能够保证一旦singleton被初始化后,多线程下可以正确的处理它。

总结

  1. 单例模式确保程序中一个类最多只能有一个实例
  2. 单例模式提高一个全局访问点获取唯一的实例
  3. 单例模式依赖于私有构造器,一个静态变量和一个静态方法
  4. 单例模式再多线程环境下可能失效,需要使用一定的改进方法

本文参考自《Head First 设计模式(中文版)》
作者:smartpig
微信公众号:SmartPig
个人博客:http://smartpig612.club

猜你喜欢

转载自blog.csdn.net/tianc_pig/article/details/89636288