java创建对象的步骤
介绍
当我们有一定的java基础的时候会觉得创建对象不就是使用new关键字创建一个对象嘛。还能有什么步骤?
其实不然JVM的机制问题创建步骤其实包含了三步:
- 分配内存空间
- 执行构造器来初始化对象
- 将创建的对象指向内存空间
但是,JVM有时为了性能的问题会进行指令重排,虽然平时使用的时候没有什么问题,但是在多线程的情况下可能会出现问题
问题
比如我们的单例模式中的懒汉式:
//DCL懒汉式(双重检测懒汉式)
public class SingletonDemo03 {
//私有化类构造器
private SingletonDemo03(){
}
//2.类初始化的时候,并不立即加载该对象
private static SingletonDemo03 instance;
//3.提供获取该对象的公开方法
public static SingletonDemo03 getInstance(){
if(instance == null){
synchronized (SingletonDemo03.class){//锁住这个类,进来判断这次调用是否是第一次,如果是就创建,如果不是直接跳过
if (instance == null){
instance = new SingletonDemo03();
}
}
}
return instance;
}
}
这样看,这个单例模式没有问题,进入getInstance()方法,先判断是否创建了该实例,如果创建了直接返回,没有创建进行加锁再判断最后再初始化。这样不是直接避免了多线程吗?
按照上面创建对象的逻辑,其实是有问题的,如果没有进行指令重排(1->2->3)当然不会有问题,但是如果进行了指令重排,按照1->3->2进行呢?
- 第一个线程进来判断没有初始化加锁再判断还没有那么创建对象当创建对象的步骤进行到1->2的时候CPU调度执行第二个线程进来了
- 第二个线程进来,发现instance这个对象已经指向了一个分配的内存空间(但是没有执行构造器初始化因为第一个线程还没有执行创建对象的第三步),ok,直接返回使用,然后使用就报null指针异常。因为这个对象还没有初始化。
如何解决呢?
出现这个问题的原因就是JVM进行了指令重排,我们可以使用volatile关键字,对instance进行修饰,这样就可以防止指令重排了:
//DCL懒汉式(双重检测懒汉式)
public class SingletonDemo03 {
//私有化类构造器
private SingletonDemo03(){
}
//2.类初始化的时候,并不立即加载该对象
private volatile static SingletonDemo03 instance;
//3.提供获取该对象的公开方法
public static SingletonDemo03 getInstance(){
if(instance == null){
synchronized (SingletonDemo03.class){//锁住这个类,进来判断这次调用是否是第一次,如果是就创建,如果不是直接跳过
if (instance == null){
instance = new SingletonDemo03();
}
}
}
return instance;
}
}