Java面试题(二十三)DCL单例
懒汉式单例
private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE == null) {INSTANCE = new SingletonInstance();}return INSTANCE;}
构造方法私有化,然后判断是否为空,如果是空,就new一个实例对象,单线程下看似没什么问题,但是如果是多线程,就会有问题了
如果有多个线程同时进入if (INSTANCE == null) 这个判断,那就会生成多个对象了,就不是单例对象了
好我们来加锁看下效果
private static SingletonInstance INSTANCE;private SingletonInstance(){}public synchronized static SingletonInstance getInstance() {if (INSTANCE == null) {INSTANCE = new SingletonInstance();}return INSTANCE;}
这样加上锁之后是不是就解决了多个线程的问题呢
但是有个问题,就是锁粒度太大了,加在方法上,那有100个线程一上来就会先竞争锁,性能非常低,我们把锁粒度缩小点
private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {synchronized (SingletonInstance.class){if (INSTANCE == null) {INSTANCE = new SingletonInstance();}}return INSTANCE;}
把synchronized 放在 方法里面,锁粒度降低了,但是还是会存在100个线程进来就竞争锁,性能还是很低,接着优化
private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE == null) {synchronized (SingletonInstance.class){if (INSTANCE == null) {INSTANCE = new SingletonInstance();}}}return INSTANCE;}
我们在synchronized外层套一个是否为空的判断,如果100个线程进入方法,此时第一个线程拿到锁,然后new了算力对象,然后释放锁资源,此时第2个线程会先判断是否为空,如果不为空直接返回对象,如果是空然后再去拿锁,拿到锁之后又会做个判断是否为空,因为第一个线程已经创建实例,所以这里判断不为空直接返回对象,后面第3,第4 … 等线程 都是一样的逻辑
这样通过Double Check Lock 双重检查锁的机制解决了多线程的问题,那么这样就完美的解决多线程问题了吗?
Java创建对象过程
我们来看下Java创建对象的过程,主要分为3步
- new 一个对象 分配空间,赋默认值
- 调用构造方法,初始化赋值
- 将引用变量指向这个内存地址
jvm并不是完全按照123这个步骤去创建对象的,有可能是先执行第一步,再执行第三步,然后执行第二步,这就是Java中的指令重排序现象,结合刚刚上面说的,如果现在有100个线程,第一个线程new了一个实例对象,也就是完成第一步,然后执行第三步,此时这个对象就是一个半成品,因为他还没有执行第2步,这样的话如果第二个线程进来,判断是否为空,不为空,直接返回实例对象,此时的这个对象就是个半成品,那么如果解决这个问题呢?
volatile关键字
volatile关键字有个特性就是禁止指令重排序,就是必须按照123这个步骤往下执行,来看下代码
//volatile 关键字private static volatile SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE == null) {synchronized (SingletonInstance.class){if (INSTANCE == null) {INSTANCE = new SingletonInstance();}}}return INSTANCE;}