(七)单例模式
2019-08-05 本文已影响0人
feiry
顾名思义,就是只能有一个实例,单例模式的运用场景非常多,有多种创建方法
饿汉式
public class Singleton {
private Singleton() {
}
private static final Singleton singleton = new Singleton();
public static Singleton getInstance() {
return singleton;
}
}
利用类加载机制,再加载类的时候就初始化好了实例,调用方法直接获取即可
特点:
1.线程安全
2.在加载类时初始化
懒汉式
其他懒汉式写法不讨论,直接使用正确的懒汉式写法
class Singleton2 {
private Singleton2() {
}
private static volatile Singleton2 singleton;
public static Singleton2 getInstance() {
if (null == singleton) {//1.
synchronized (Singleton2.class) {
if (null == singleton) {//2.
singleton = new Singleton2();
}
}
}
return singleton;
}
}
使用双检索+volatile 来保证线程安全、效率和懒加载问题
- 第一个判空是保证效率问题,如果成员变量非空直接返回
- synchronized保证线程安全,如果多个线程进入那么只有一个线程可以进入到new环节
- 如果两个线程都通过了第一个判空的步骤
线程一进入同步块,线程二等待,这时变量是null所以会new对象,然后线程一出同步块。
线程二进入同步块,发现变量不为null了,直接出同步块
所以第二个判空是保证只实例化一次 - 为什么要加volatile?
volatile的作用有哪些?线程可见性,禁用指令重排序,这里就是用到了禁用指令重排序的特性
new关键字不是一个原子操作,它有三步:1.创建对象(分配内存空间),2.初始化对象,3.返回地址给引用变量
由于JVM内部的优化机制,指令可能会重新排序
例如线程A进入同步块,开始new对象,由于指令重排序,可能第三步最先执行,那么成员变量这时候已经有了一个地址值。
这时线程B进入方法(没有进入同步块),发现第一个判断singleton不是空,然后就直接返回了这个引用变量,此时线程A还未初始化对象,但是线程B已经把变量返回出去了,那么外部使用这个变量就会发生异常,这就为什么要加volatile的原因
静态内部类
class Singleton3 {
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.singleton;
}
private static class SingletonHolder{
private static final Singleton3 singleton = new Singleton3();
}
}
同样例如类加载器的机制,只有当时用getInstance方法时,才会实例化变量,和饿汉式比,同样没有使用synchronized,但是多一个懒加载的效果
枚举
enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
大牛推荐的单例模式实现方式,代码简单,并且使用反射和序列化后的对象依然是同一个内存地址,上面的实现方式都不能避免反射攻击,但是没什么人用
企业中用java开发基本没有不用spring框架的,spring的IOC容器中默认就是单例的,使用spring就行