设计模式-单例模式
单例模式就是在访问某个类的时候它只有唯一的实例
单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
java常见的单例模式
1.饿汉单例
饿汉单例模式,顾名思义,就是类加载的时候实例化一个对象出来
代码实现:
public class Singleton{ private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return instance; } }
优点:类构造为private,保证了外部类不能构造它
在类加载的时候创建好一个实例,不存在多线程创建多个实例的问题
缺点:初始化的时候构造,如果该单例未被使用,浪费内存资源
2.懒汉单例
懒汉单例模式解决了饿汉单例模式的缺点,在使用的时候再对它进行实例化
代码实现:
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
由于懒汉单例模式并没有考虑线程安全,故加synchronized🔒,来保证线程安全
优点:按需创建,节约资源
缺点:加了线程锁,多线程环境初始化的时候性能不好
3.登记单例(spring单例实现是登记单例和双重锁的实现)
4.双重校验锁
为解决加锁懒汉单例引起的性能问题,出现了双重校验锁单例,
代码实现如下:
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) {//2 instance = new Singleton(); } } } return instance; } }
优点:在懒汉模式的基础上,解决了多线程并发的性能问题
缺点:java指令重排优化引起错误,
指令重排:
重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。这个问题的关键就在于由于指令重排优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。
解决方案:加volatile,加volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的,从而避免了上面说到的问题
volatile详解
首先我们要先意识到有这样的现象,编译器为了加快程序运行的速度,对一些变量的写操作会先在寄存器或者是CPU缓存上进行,最后才写入内存.
而在这个过程,变量的新值对其他线程是不可见的.而volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行!
5.静态内部类
代码实现:
public class Singleton{ private static class SingletonHolder{ public static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return SingletonHolder.instance; } }
优点:内部类自己实现,保证延迟加载,保证线程安全