浅析java设计模式之单例模式

单例模式顾名思义就是只含有一个实例,一个实例复用,达到减少创建对象的开销以及大大节省资源的效果,是java23种设计模式最简单应用最多的设计模式;单例模式的实现方法主要分为饿汉式和懒汉式两大类,

饿汉式

public class SimpleInstance {
    public static SimpleInstance simpleInstance = new SimpleInstance();

    private SimpleInstance() {}

    public static SimpleInstance getInstance() {
        return simpleInstance;
    }
}

浅析:
类装载时就已经创建了实例,是天然的线程安全写法,但是因为不管是否使用都创建了实例,可能造成资源的浪费

在springboot架构下,所有的被注册为Bean的类默认都是单例模式,并且默认使用饿汉式,即在springboot项目运行时,所有的Bean就被实例化了


懒汉式

public class SimpleInstance {
    public static SimpleInstance simpleInstance;

    private SimpleInstance() {}

    public static SimpleInstance getInstance() {
        if (simpleInstance == null) {
            simpleInstance = new SimpleInstance();
        }
        return simpleInstance;
    }
}

浅析:
延迟加载,弥补了饿汉式浪费资源的缺点,但是只适合在单线程下使用,多线程下在if (simpleInstance == null)判断中,还未来得及往下执行,另一个线程也通过了这个判断语句,导致创建了多个实例,因此多线程下这种方式不可用

懒汉式(synchronized同步方法)

public class SimpleInstance {
    public static SimpleInstance simpleInstance;

    private SimpleInstance() {}

    public static synchronized SimpleInstance getInstance() {
        if (simpleInstance == null) {
            simpleInstance = new SimpleInstance();
        }
        return simpleInstance;
    }
}

浅析:
用synchronized 修饰获取实例的方法,解决上面那种方式的线程安全问题,但是synchronized 修饰方法太重量级了,在高并发下,在获取到锁的线程拿到实例前其他线程都会被阻塞,造成获取实例性能低下,因此不推荐使用

懒汉式(synchronized同步代码块)

public class SimpleInstance {
    public static SimpleInstance simpleInstance;

    private SimpleInstance() {}

    public static SimpleInstance getInstance() {
        if (simpleInstance == null) {
            synchronized (SimpleInstance.class) {
                if (simpleInstance == null) {
                    simpleInstance = new SimpleInstance();
                }
            }
        }
        return simpleInstance;
    }
}

浅析:
针对synchronized 同步方法方式的缺点,把synchronized 锁改为修饰代码块,并进行双重校验,第一个if 判断是在实例被创建后直接返回就不需要进入竞争同步锁的逻辑,这样解决了synchronized 同步方法方式高并发下性能低下的缺点;第二个if 判断是防止多个线程通过了第一个if 判断再等待获取同步锁再次创建实例,起到保证保证线程安全作用
这种方式看似好像已经完美解决上面几种方式的缺点,但是实际上还是有问题
问题在于new一个SimpleInstance对象的操作不是原子性,它分为以下几步

  1. 给SimpleInstance对象分配内存空间
  2. 初始化SimpleInstance对象
  3. 将SimpleInstance对象指向其被分配的内存空间

由于JVM会优化指令的特性,这三条指令其实顺序不一定是按照上述顺序,JVM会对指令重排序,如果上述指令发生了重排序,顺序会变成以下这样

  1. 给SimpleInstance对象分配内存空间
  2. 将SimpleInstance对象指向其被分配的内存空间
  3. 初始化SimpleInstance对象

即第二步和第三步顺序调换,这样在多线程下,可能会发生这样的情况:线程A拿到synchronized同步锁去new一个SimpleInstance对象,执行到完第二步后,线程B抢占到cpu时间片,此时simpleInstance对象已经被分配了内存空间,但对象没有被初始化,而线程B执行第一个 if (simpleInstance == null)语句,由于simpleInstance对象已经被分配了内存空间,得到结果会是flase并返回一个null对象,这种结果显然是不允许的,因此这种方式不可用

懒汉式(synchronized同步代码块 + volatile关键字)

public class SimpleInstance {
    public static volatile SimpleInstance simpleInstance;

    private SimpleInstance() {}

    public static SimpleInstance getInstance() {
        if (simpleInstance == null) {
            synchronized (SimpleInstance.class) {
                if (simpleInstance == null) {
                    simpleInstance = new SimpleInstance();
                }
            }
        }
        return simpleInstance;
    }
}

浅析:
针对synchronized 同步代码块的缺点,可以利用volatile禁止指令重排序的特性,用volatile关键字修饰实例变量,这样在new一个SimpleInstance对象操作永远是这样的步骤:

  1. 给SimpleInstance对象分配内存空间
  2. 初始化SimpleInstance对象
  3. 将SimpleInstance对象指向其被分配的内存空间

这样就不会存在返回一个null对象的现象了,推荐使用这种方式

懒汉式(静态代码块)

public class SimpleInstance {
    public static SimpleInstance simpleInstance;

    static {
        simpleInstance = new SimpleInstance();
    }
    
    private SimpleInstance() {}

    public static SimpleInstance getInstance() {
        return simpleInstance;
    }
}

浅析:
这种方式和饿汉式很类似,不同的是这种方式创建实例对象放在静态代码块里,静态代码块是JVM在类装载时不会立即执行,只有在类初始化的时候才会有且只执行一次,利用JVM这一特性保证了线程安全同时也实现了延迟加载,这种方式性能比synchronized同步代码块 + volatile关键字方式更好,因此推荐使用

懒汉式(枚举)

public enum  SimpleInstance {
    INSTANCE;

    private SimpleInstance() {}
    
}

浅析:
枚举时JDK1.5特性,它是天然的线程安全以及延迟加载,上面的几种方法都存在一个安全问题,那就是可以通过反射创建多个实例,而枚举是无法通过反射创建实例,完美解决这一问题,并且枚举获取实例的性能是最优的,因此,枚举方式可以说是最完美的单例模式的实现方式,推荐使用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值