JUC并发编程 --单例模式
单例模式是java中最简单的设计模式之一。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
使用场景:
要求产生唯一序列号。
WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。
饿汉式单例模式
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
package com.ning.single;/*** @author 16790* 饿汉式单例模式*/public class Hungry {private Hungry(){}private static Hungry hungry=new Hungry();public static Hungry getInstance(){return hungry;}}
懒汉式单例模式
package com.ning.single;/*** @author 16790* 懒汉式单例模式不支持多线程,在多线程下,这种模式不安全*/public class LazyMan {private LazyMan(){}private static LazyMan lazyMan;public static LazyMan getInstance(){if (lazyMan==null){lazyMan=new LazyMan();}return lazyMan;}}
DCL 懒汉式单例模式(DCL即双检锁 double-checked locking)
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
package com.ning.single;/*** @author 16790* DCL懒汉式单例模式*/public class DCLLazyMan {private DCLLazyMan(){}//这里为什么要使用volatile?/**dclLazyMan=new DCLLazyMan();这个不是原子操作,要防止指令重排为什么不是原子操作?1.分配内存空间2.执行构造方法,初始化对象3.把这个对象指向这个空间*/private static volatile DCLLazyMan dclLazyMan;public static DCLLazyMan getInstance(){if (dclLazyMan==null){//同步代码块synchronized (DCLLazyMan.class){dclLazyMan=new DCLLazyMan();}}return dclLazyMan;}}
静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
package com.ning.single;/*** @author 16790* 静态内部类*/public class StaticClass {private StaticClass(){}public static StaticClass getInstance(){return InnerClass.STATIC_CLASS;}//内部类public static class InnerClass{private static final StaticClass STATIC_CLASS=new StaticClass();}}
枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
package com.ning.single;/*** @author 16790* 枚举*/public enum EnumSingle {INSTANCE;public EnumSingle getInstance(){return INSTANCE;}}
单例不安全,反射和序列化可以破坏单例
使用反射创建对象
package com.ning.single;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;/*** @author 16790* 懒汉式单例模式*/public class LazyMan {private LazyMan(){}private static LazyMan lazyMan;public static LazyMan getInstance(){if (lazyMan==null){lazyMan=new LazyMan();}return lazyMan;}}class test{public static void main(String[] args) throws Exception {LazyMan lazyMan=LazyMan.getInstance();Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);constructor.setAccessible(true);LazyMan lazyMan1 = constructor.newInstance();System.out.println(lazyMan);System.out.println(lazyMan1);}}测试结果:com.ning.single.LazyMan@1b6d3586com.ning.single.LazyMan@4554617c
分析:从结果来看,创建了两个单例对象,这样就破坏了单例模式。破坏的原因是将构造方式的private的检查,通过constructor.setAccessible(true)进行了屏蔽。
解决办法:将构造方法的调用次数设置一个标志,来进行表示调用次数,超过一次之后,在调用构造方法直接抛出异常。
package com.ning.single;import java.lang.reflect.Constructor;/*** @author 16790* DCL懒汉式单例模式*/public class DCLLazyMan {private static boolean flag=false;private DCLLazyMan(){synchronized (DCLLazyMan.class){if (flag==false){flag=true;}else{throw new RuntimeException("不能创建多个实例");}}}private static volatile DCLLazyMan dclLazyMan;public static DCLLazyMan getInstance(){if (dclLazyMan==null){synchronized (DCLLazyMan.class){dclLazyMan=new DCLLazyMan();}}return dclLazyMan;}}class Test{public static void main(String[] args) throws Exception{Constructor<DCLLazyMan> constructor = DCLLazyMan.class.getDeclaredConstructor(null);constructor.setAccessible(true);DCLLazyMan dclLazyMan = constructor.newInstance();DCLLazyMan dclLazyMan2= constructor.newInstance();System.out.println(dclLazyMan);System.out.println(dclLazyMan2);}}
结果:
序列化破坏单例模式
测试
package com.ning.single;import java.io.*;import java.lang.reflect.Constructor;/*** @author 16790* DCL懒汉式单例模式*/public class SerializableTest implements Serializable {private static boolean flag=false;private SerializableTest(){synchronized (SerializableTest.class){if (flag==false){flag=true;}else{throw new RuntimeException("不能创建多个实例");}}}private static volatile SerializableTest serializableTest;public static SerializableTest getInstance(){if (serializableTest==null){synchronized (SerializableTest.class){serializableTest=new SerializableTest();}}return serializableTest;}}class sTest{public static void main(String[] args) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));oos.writeObject(SerializableTest.getInstance());ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile")));SerializableTest serializableTest1 = (SerializableTest) ois.readObject();//判断是否是同一个对象System.out.println(serializableTest1);System.out.println(SerializableTest.getInstance());}}
结果:
输出的结果不一样说明:
通过对该类的序列化与反序列化得到的一个对象是一个新对象。
为什么序列化会破坏单列模式?
这里重点看一下readOrdinaryObject方法的代码:
private Object readOrdinaryObject(boolean unshared)throws IOException//此处省略部分代码Object obj;try {/**isInstantiable():如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。*/obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}//此处省略部分代码if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}
所以也可以解释为什么序列化可以破坏单例了?
序列化会通过反射调用无参数的构造方法创建一个新的对象。
防止序列化破坏单例模式:
只要在该类中定义readResolve()方法就可以解决该问题:
package com.ning.single;import java.io.*;import java.lang.reflect.Constructor;/*** @author 16790* DCL懒汉式单例模式*/public class SerializableTest implements Serializable {private static boolean flag=false;private SerializableTest(){synchronized (SerializableTest.class){if (flag==false){flag=true;}else{throw new RuntimeException("不能创建多个实例");}}}private static volatile SerializableTest serializableTest;public static SerializableTest getInstance(){if (serializableTest==null){synchronized (SerializableTest.class){serializableTest=new SerializableTest();}}return serializableTest;}public Object readResolve(){return serializableTest;}}class sTest{public static void main(String[] args) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));oos.writeObject(SerializableTest.getInstance());ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("tempFile")));SerializableTest serializableTest1 = (SerializableTest) ois.readObject();//判断是否是同一个对象System.out.println(serializableTest1);System.out.println(SerializableTest.getInstance());}}
原理:
还是在readOrdinaryObject方法中
//此处省略部分代码/**hasReadResolveMethod():如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回trueinvokeReadResolve():通过反射的方式调用要被反序列化的类的readResolve方法。*/if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}
