单例单例,就是只允许实例化一个对象。一般实现方式也就是将构造方法私有化,然后对外暴露一个获取实例的接口
单例 可以说源自于设计模式中的单例模式吧,多种实现演进,变得越来越靠谱
最早单例模式分为懒汉式 饿汉式
懒汉式:
懒汉式很简单啊,就是全局变量声明时候直接new了,但是这样会有个占用内存的问题,因为如果这个实例用不到,那不是白白浪费空间了,尤其是项目庞大寸土寸金的JVM内存空间
懒汉式的有点也很明显,简单好用,而且不会有乱七八糟的线程安全问题,所以单例的这个实例一定会用到的话 用懒汉式直接实现也无妨
饿汉式:
最原始的饿汉式,外部获取实例的时候先判断 实例是不是null,如果空的直接new一个
单线程的世界里这样搞绝对没问题,但是问题就出在了多线程的情况下,当多个线程同时调用getInstance() ,然后同时判断了singlon为null,也就会new两个实例出来,违反了单例。
怎么办呢,为了多线程安全 那就加锁呗
这下放心了,多线程来调用这个方法的时候,不许抢都得给我排队串行来执行,也就保证了只会new一次Singlon
但是还有问题,synchronized 这么重量级的锁 直接加到了这个方法上是不是不太好啊,如果多个线程经常频繁的会调用这个方法,都串行执行那也太难受了 (即时jdk1.8 后synchronized会有锁升级)
so 尽量轻量一点 只在if条件确定了singlon==null的时候 再加锁行不行?
if(singlon == null){
synchronized(this){
singlon = new Singlon();
}
}
不行啊,如果恰好就有两个同时判断了singlon == null 然后同时进入到了if的代码块,加锁并没有阻止第二个线程new Singlon啊
应运而生了double-check 一次判断不够用 我再来一次被
在锁里在判断一次 这下放心了吧
以为到这万事大吉了,其实并不然.虽然这种情况发生的概率不大,指令重排序导致的多线程安全问题
singlon = new Singlon(); 这一行代码 在底层其实是三条指令完成的
1.开辟一块堆内存空间
2. 初始化一个Singlon对象
3. singlon声明指向这个对象
由于指令重排,很有可能 2 3步进行重排,1 3 2 的顺序执行,
但第一个线程执行完了1 3步,这个时候,第二个线程进入方法 判断了一下singlon不为null 就直接返回了,事实上这个 singlon表面上不为null 但是还没来得及实实在在的实例化,导致直接return出问题
那咋办呢 加volatile 防止指令重排呗
实现单例的方式还有很多 比如常用的静态内部类 静态代码块 或者更简单直接用枚举它不香吗? 这里就不一一列举了 over