它是由一下3个指令完成的:
给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间地址
JVM编译时进行指令重排序可能打乱上面三个指令的执行顺序,也就是说可能先直行来1,3然后执行2。那么有这么一种情况当执行好1和3,instance不为null,新进入的线程在判断第一个null时就会直接返回一个没有执行2步骤的实例,如此就有不符合期望了。这的确是个经典的场景。
额外阅读
如果我们在实例初始化后,将第三步,分开写,似乎可以解决这个问题,代码如下:
public staticSingleton getInstance() {if (instance == null) {synchronized (Singleton.class) {
Singleton temp=instance;if (temp == null) {synchronized (Singleton.class) {
temp= newSingleton();
}
instance=temp;
}
}
}returninstance;
}
volatile关键字事实上是对编译时的重排序进行了屏障。具体各家说法可以阅读下面的文章:
扩展阅读
由上可以感受到,在累加载时就初始化好实例,会有很多需要考虑的东西,那么如果在编译阶段就实例化好,如此就可以避免并发带来的问题。
那就是所谓的饿汉式单例:
public classSingleton{//类加载时就初始化
private static final Singleton instance = newSingleton();privateSingleton(){}public staticSingleton getInstance(){returninstance;
}
}
当然这样做自然有自己的弊端,就是这个单例在没有被使用到的时候就已经需要实例化出来,如此就会占用无谓占用内存,如果这个实例初始化复杂占用资源,而实际未必会使用就比较尴尬了。
或者说,这种方式实例化将无法实现依赖外部参数实例化的场景。
还有一种推荐写法:
public classSingleton {private static classSingletonHolder {private static final Singleton INSTANCE = newSingleton();
}privateSingleton (){}public static finalSingleton getInstance() {returnSingletonHolder.INSTANCE;
}
}
还有一种大师推荐的写法,有没有很高大上:
public enumEasySingleton{
INSTANCE;
}
我们来看看如何破坏单例:
1,序列化与反序列化
当然我们前面写的代码不需要序列化和反序列化,就没有这个问题了,只是说送这个方面我们考虑如何破坏它,参考如下例子:
public class Singleton implementsSerializable{private volatile staticSingleton singleton;privateSingleton (){}public staticSingleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {
singleton= newSingleton();
}
}
}returnsingleton;
}
}public classSerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出
public static void main(String[] args) throwsIOException, ClassNotFoundException {//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois= new ObjectInputStream(newFileInputStream(file));
Singleton newInstance=(Singleton) ois.readObject();//判断是否是同一个对象
System.out.println(newInstance ==Singleton.getSingleton());
}
}//false
2,反射
public classSingleton {public static final Singleton INSTANCE = newSingleton();privateSingleton() {
}publicSingleton getInstance() {returnINSTANCE;
}public static void main(String[] args) throwsException {//反射机制破坏单例模式
Class clazz = Singleton.class;
Constructor c=clazz.getDeclaredConstructor();//反射机制使得private方法可以被访问!!!
c.setAccessible(true);//判断反射生成的对象与单例对象是否相等
System.out.println(Singleton.INSTANCE ==c.newInstance());
}
}
破坏单例的原理就是,不走构造函数即可产生实例的方式,因为我们只关闭了构造函数。
至此,对java单例有一个比较全面的认识,牵涉到大量知识点,需要继续挖掘。
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。