最近开始学习一些设计模式相关内容,这篇文章也不算是完全原创,大部分来自于左潇龙大神讲解单例模式的博客不同写法中一些关键点的总结。
单例模式是最常见的也是用的最多的设计模式之一,判断是否单例模式可以通过一个类是否是单例就看在整个应用中同一时刻,只有一个状态,如果有两个或两个以上实例就会发生错误
目的:节约内存空间和减小GC消耗
饿汉模式:每一次访问其他静态域都会实例化创建新对象开销大,线程安全
public class Singleton { private static Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
懒汉模式:加载类速度快,对象获取速度慢,线程不安全
1.静态实例:确保每个类中是唯一
2.私有化构造方法:防止调用方创造多个实例
3.获取实例静态方法:非静态方法要拥有实例才能调用
4.判断为null时创建,不为null返回
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if(singleton==null){ singleton = new Singleton(); } return singleton; } }
考虑多线程:
单例的不好实现:方法同步块造成有一个线程访问方法时候,其他线程都要等待,性能很差
public class BadSynchronizedSingleton { //一个静态的实例 private static BadSynchronizedSingleton synchronizedSingleton; //私有化构造函数 private BadSynchronizedSingleton(){} //给出一个公共的静态方法返回一个单一实例 public synchronized static BadSynchronizedSingleton getInstance(){ if (synchronizedSingleton == null) { synchronizedSingleton = new BadSynchronizedSingleton(); } return synchronizedSingleton; } }
双重加锁实现:
- JVM创建对象分三步:分配内存,初始化构造器,将对象指向内存地址
- 但是由于存在JVM字节码调优,如指令重排序优化问题会将后两步骤顺序颠倒,会导致如果有一个线程先分配地址指向SynchronizedSingleton,再初始化SynchronizedSingleton,后一个线程访问getInstance方法后会认为SynchronizedSingleton已经初始化直接返回引用,如果前一个线程未初始化完毕就会导致拿到错误的实例
- 可以使用volatile将读和取变成不可拆分的操作,禁止指令重排序优化,保证线程可见性
public class SynchronizedSingleton { private static SynchronizedSingleton synchronizedSingleton; private SynchronizedSingleton() { } public static SynchronizedSingleton getInstance() { if (synchronizedSingleton == null) { synchronized (SynchronizedSingleton.class) { if (synchronizedSingleton == null) { synchronizedSingleton = new SynchronizedSingleton(); } } } return synchronizedSingleton; } }
内部类实现:
public class Singleton { private Singleton(){} public static Singleton getInstance(){ return SingletonInstance.instance; } private static class SingletonInstance { static Singleton instance = new Singleton(); } }
解决三个问题:
1.Singleton实现最多只有一个实例
2.多线程并发访问不会创建多个实例
3. 多线程并发访问不会出现初始化未完成使用错误的实例