java单例-积木系列
Posted 干掉自己
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java单例-积木系列相关的知识,希望对你有一定的参考价值。
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
线程安全问题:当多线程同时调用getInstance()方法,同时判断出instance,进入实例化操作,单利就不复存在。
为了线程安全,那我们对getInstance()方法进行同步化:
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
synchronized修饰保证同一时间只有一个线程可以执行getInstance方法,如此可以保证单例的线程安全。但是同步粒度似乎太大,事实上当实例初始化后我们并不需保证一个个线程排队来取这个实例。
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getSingleton() { if (instance == null) { //Single Checked synchronized (this) { if (instance == null) { //Double Checked instance = new Singleton(); } } } return instance ; } }
同步快外面和里面check两次,保证在进入同步块的线程不产生新的实例。
instance = new Singleton();
上面这个代码不是一个原子操作,即无法被翻译成一个指令完成。
给 instance 分配内存
调用 Singleton 的构造函数来初始化成员变量
将instance对象指向分配的内存空间地址
JVM编译时进行指令重排序可能打乱上面三个指令的执行顺序,也就是说可能先直行来1,3然后执行2。那么有这么一种情况当执行好1和3,instance不为null,新进入的线程在判断第一个null时就会直接返回一个没有执行2步骤的实例,如此就有不符合期望了。这的确是个经典的场景。
如果我们在实例初始化后,将第三步,分开写,似乎可以解决这个问题,代码如下:
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { Singleton temp = instance; if (temp == null) { synchronized (Singleton.class) { temp = new Singleton(); } instance = temp; } } } return instance; }
由上可以感受到,在累加载时就初始化好实例,会有很多需要考虑的东西,那么如果在编译阶段就实例化好,如此就可以避免并发带来的问题。
那就是所谓的饿汉式单例:
public class Singleton{ //类加载时就初始化 private static final Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; } }
当然这样做自然有自己的弊端,就是这个单例在没有被使用到的时候就已经需要实例化出来,如此就会占用无谓占用内存,如果这个实例初始化复杂占用资源,而实际未必会使用就比较尴尬了。
或者说,这种方式实例化将无法实现依赖外部参数实例化的场景。
还有一种推荐写法:
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
还有一种大师推荐的写法,有没有很高大上:
public enum EasySingleton{ INSTANCE; }
我们来看看如何破坏单例:
public class Singleton implements Serializable{ private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } public class SerializableDemo1 { //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记 //Exception直接抛出 public static void main(String[] args) throws IOException, 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(new FileInputStream(file)); Singleton newInstance = (Singleton) ois.readObject(); //判断是否是同一个对象 System.out.println(newInstance == Singleton.getSingleton()); } } //false
2,反射
public class Singleton { public static final Singleton INSTANCE = new Singleton(); private Singleton() { } public Singleton getInstance() { return INSTANCE; } public static void main(String[] args) throws Exception { // 反射机制破坏单例模式 Class clazz = Singleton.class; Constructor c = clazz.getDeclaredConstructor(); // 反射机制使得private方法可以被访问!!! c.setAccessible(true); // 判断反射生成的对象与单例对象是否相等 System.out.println(Singleton.INSTANCE == c.newInstance()); } }
破坏单例的原理就是,不走构造函数即可产生实例的方式,因为我们只关闭了构造函数。
至此,对java单例有一个比较全面的认识,牵涉到大量知识点,需要继续挖掘。
让我们继续前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不会成功。
以上是关于java单例-积木系列的主要内容,如果未能解决你的问题,请参考以下文章