单例模式的理解和示例
一、是什么
确保一个类只有一个实例,并提供一个全局访问点
一般分类两大类: 饿汉模式、懒汉模式
使用: 以前在线白鹭H5游戏时,因为有很多的场景类, 而每个场景类不需要创建很多遍, 所以使用单例模式
二、示例
1. 饿汉模式
/** * 饿汉模式, 线程安全, 但默认就创建实例, 占用空间 */ public class Singleton1 { private static final Singleton1 instance = new Singleton1(); private Singleton1() { } public static Singleton1 getIntance() { return instance; } }
用空间换时间,默认就创建实例,所以没有线程安全问题
2. 懒汉模式
/** * 懒汉模式, 线程不安全 */ public class Singleton2 { private static Singleton2 instance = null; private Singleton2() { } public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
现在不安全在于,多个线程访问getInstance()时,当一个线程已经初始化了,而另外一个线程并没有感知,又重新创建了实例,这时候就不是单例
2.1 双检锁 Double-check
/** * 懒汉模式--双检索 */ public class SingletonDoubleCheck { private static SingletonDoubleCheck instance = null; private SingletonDoubleCheck() { } private static SingletonDoubleCheck getInstance() { if (instance == null) { synchronized (SingletonDoubleCheck.class) { if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } }
为了在懒汉模式的基础上,保证线程安全, 出现了双检锁的设计,但是有出现了另一个问题。
在new SingletonDoubleCheck()时,是非原子性的,实际分为三步
- new 分配内存空间
- 初始化对象
- 将对象指向刚分配的内存空间
但JVM编译器,为了性能考虑,可能重新排序2,3两个, 变为:
- new 分配内存空间
- 将对象指向刚分配的内存空间
- 初始化对象
举例说明:
线程1检查到instance为空,获取锁,再次检查instance为空,为instance分配内存空间,指向内存空间,这时线程2检查到instance不为空,直接返回instance,但此时对象还没有初始化完成
2.2 双检锁 线程安全
/** * 使用volatile关键字的双检锁 */ public class SingletonDoubleCheck2 { /** * volatile关键字保证我在锁instance时, 禁止JVM重排序 */ private volatile static SingletonDoubleCheck2 instance = null; private SingletonDoubleCheck2() { } private static SingletonDoubleCheck2 getInstance() { if (instance == null) { // 再次减少锁的范围, 只锁instance变量 synchronized (instance) { if (instance == null) { instance = new SingletonDoubleCheck2(); } } } return instance; } }
使用volatile关键字来禁止JVM重排序
3.3 内部类实现
/** * 静态内部类实现 -- 延迟加载 * * 天生线程安全 */ public class Singleton3 { /** * 私有化构造 */ private Singleton3() { System.out.println("初始化"); } /** * 静态内部类 */ private static class InnerObject{ private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance() { return InnerObject.instance; } public static void main(String[] args) { getInstance(); } }
3.4 静态代码实现
/** * 懒汉模式 -- 静态代码块实现 */ public class Singleton4 { private static Singleton4 instance = null; static { instance = new Singleton4(); } public static Singleton4 getInstance() { return instance; } }
外部类加载时并不需要立即加载内部类,所以可以起到延时加载的目录,
三、总结
单例模式是一个创建型的设计模式,能够帮助开发者创建一个唯一的实例
使用的还是挺频繁的