模式定义
单例模式确保一个类只有一个实例,并且对外提供全局的访问方法。
单例模式有三个特点:
一是一个类只有一个实例
二是它必须自行创建这个实例
三是它必须对外提供全局访问方法
模式类图
以下是单例模式的UML类图
代码实现
一般包含三个要素:
1.私有的静态实例对象 private static instance
2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例) private Singleton(){}
3.公有的、静态的、访问该实例对象的方法 public static Singleton getInstance(){}
懒汉式单例模式(线程不安全-延迟加载)
懒汉式就是应用刚启动的时候不创建实例,当外部调用获取该类的实例方法时才创建。是以** 时间换空间 ** 。
实例被延迟加载,这样做的好处是,如果没有用到该类,那么静态变量instance就不会被实例化,从而节省资源。
懒汉式单例模式是线程不安全的,在多线程环境下instance可能会被多次实例化。
/** * Singleton 懒汉式单例模式(线程不安全-延迟加载) */public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
懒汉式单例模式(线程安全-延迟加载)
对getInstance()方法加锁之后,同一时间只有一个线程访问该方法,这样就避免了instance被多次实例化。
getInstance()方法由于加了synchronized关键字,当有一个线程获得锁并访问该方法时,其他线程处于阻塞状态,一定程度上对性能有所损耗。
/** * Singleton 懒汉式单例模式(线程安全-延迟加载) */public class Singleton { private static Singleton instance; private Singleton(){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
饿汉式单例模式(线程安全)
饿汉式就是应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。是以 ** 空间换时间 ** 。
饿汉式单例在类初始化时就创建好一个静态的对象供外部使用,所以本身就是线程安全的。
/** * Singleton 饿汉式单例模式(线程安全) */public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } }
双重校验锁单例模式(线程安全-延迟加载)
/** * Singleton 双重校验锁单例模式(线程安全-延迟加载) * * @author renguangli 2018/7/23 16:59 * @since JDK 1.8 */public class Singleton { private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
在进入synchronized块之前加入判空逻辑,只有instance在没有被实例化之前才进入同步块,instance实例化之后就不会在进入同步块里了,效率当然也会提高。
是否有必要加volatile关键字?
我们都知道instance = new Singleton()这段代码是分三步运行的
1、分配内存空间
2、实例化对象
3、将instance指向分配的内存地址
由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2,这在单线程情况下自然是没有问题。但如果是多线程下,有可能获得是一个还没有被初始化的实例,以致于程序运行出错。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。所以还是有必要加上volatile关键字的。
静态内部类实现(线程安全-延迟加载)
/** * Singleton 静态内部单例模式(线程安全-延迟加载) */public class Singleton { private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton getInstance() { return SingletonHolder.instance; } }
由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
由于在调用 SingletonHolder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
所以这种形式,很好的避免了反射入侵。
基于cas实现的单例模式(线程安全-延迟加载)
/** * Singleton cas实现的单例模式(线程安全-延迟加载) */public class Singleton { public static AtomicInteger count = new AtomicInteger(0); private Singleton() {} public static Singleton getInstance() { for (;;) { Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } }
cas实现的单例模式,它可以保证获取到的实例是唯一的,但是不能保证instance被多次实例化.
基于枚举实现的单例模式
/** * 枚举实现的单例模式(线程安全) */public enum Singleton { INSTANCE; }
这是 Effective Java 极力推荐的一种,代码为各种实现中最简单的,其实现,完全是基于枚举类的特性,可以说天生受到了 JVM 的支持,而且既不用思考反射,也不用考虑多线程,对于自身的循环引用,本质上也是一个对象。
原文出处:https://www.cnblogs.com/renguangli/p/9371966.html