单例模式确保一个类只有一个实例,并且对外提供全局的访问方法。
单例模式有三个特点:
一是一个类只有一个实例
二是它必须自行创建这个实例
三是它必须对外提供全局访问方法
模式类图以下是单例模式的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; } } 饿汉式单例模式(线程安全)饿汉式就是应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。是以 ** 空间换时间 ** 。
饿汉式单例在类初始化时就创建好一个静态的对象供外部使用,所以本身就是线程安全的。
在进入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 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。
所以这种形式,很好的避免了反射入侵。