java设计模式—单例模式(包含单例的破坏)

什么是单例模式?

保证一个了类仅有一个实例,并提供一个访问它的全局访问点。java

单例模式的应用场景?

  1. 网站的计数器,通常也是采用单例模式实现,不然难以同步;
  2. Web应用的配置对象的读取,通常也应用单例模式,这个是因为配置文件是共享的资源;
  3. 数据库链接池的设计通常也是采用单例模式,由于数据库链接是一种数据库资源;
  4. 多线程的线程池的设计通常也是采用单例模式,这是因为线程池要方便对池中的线程进行控制。

单例的优缺点?

优势:数据库

  • 提供了对惟一实例的受控访问;
  • 因为在系统内存中只存在一个对象,所以能够 节约系统资源,当 须要频繁建立和销毁的对象时单例模式无疑能够提升系统的性能;
  • 避免对共享资源的多重占用

缺点:安全

  • 不适用于变化的对象,若是同一类型的对象老是要在不一样的用例场景发生变化,单例就会引发数据的错误,不能保存彼此的状态;
  • 因为单利模式中没有抽象层,所以单例类的扩展有很大的困难;
  • 单例类的职责太重,在必定程度上违背了“单一职责原则”。

单例的建立方式

1. 饿汉式

类初始化时,会当即加载该对象,线程安全,效率高。多线程

/**
 * @Author 刘翊扬
 * @Version 1.0
 */
public class SingletonHungry {

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() {}

    public static SingletonHungry getInstance() {
        return instance;
    }
}

验证:ide

public class Main {
    public static void main(String[] args) {
        SingletonHungry instance1 = SingletonHungry.getInstance();
        SingletonHungry instance2 = SingletonHungry.getInstance();
        System.out.println(instance1 == instance2);      // 结果是true
    }
}

优势:仅实例化一次,线程是安全的。获取实例的速度快
缺点:类加载的时候当即实例化对象,可能实例化的对象不会被使用,形成内存的浪费。函数

2. 使用静态代码块

/**
* @author 刘翊扬
*/
public class HungrySingleton2 {
   
   private static HungrySingleton2 instance = null;
   
   private HungrySingleton2() {}

   static {
       instance = new HungrySingleton2();
   }
   
   private HungrySingleton2() {}
   
   public static HungrySingleton2 getInstance() {
       return instance;
   }
   
}

3. 懒汉式

public class SingletonLazy {

    private static SingletonLazy instance;

    private SingletonLazy() {}

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

优势:在使用的时候,建立对象,节省系统资源
缺点:性能

  1. 若是获取实例时,初始化的工做量较多,加载速度会变慢,影响系统系能
  2. 每次获取对象,都要进行非空检查,系统开销大
  3. 非线程安全,当有多个线程同时调用 getInstance()方法,时,会有线程安全问题,可能致使建立多个对象

4. 静态内部类

/**
* @Author 刘翊扬
* @Version 1.0
*/
public class SingletonDemo03 {

   private SingletonDemo03() {}

   public static class SingletonClassInstance {
       private static final SingletonDemo03 instance = new SingletonDemo03();
   }

   public static SingletonDemo03 getInstance() {
       return SingletonClassInstance.instance;
   }
}

优点:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。优化

劣势:须要两个类去作到这一点,虽然不会建立静态内部类的对象,可是其 Class 对象仍是会被建立,并且是属于永久带的对象。网站

5. 使用枚举

枚举自己就是单例的,通常在项目中定义常量。
例如:this

/**
 * @Author 刘翊扬
 * @Version 1.0
 */
public enum  ResultCode {
    
    SUCCESS(200, "SUCCESS"),
    ERROR(500, "ERROR");
    
    private Integer code;
    
    private String msg;

    ResultCode(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
/**
 * @Author 刘翊扬
 * @Version 1.0
 */
public class User {

    private User() {}

    public static User getInstance() {
        return SingletonDemo04.INSTANCE.getInstance();
    }

    private static enum SingletonDemo04 {
        INSTANCE;
        // 枚举元素为单例
        private User user;

        SingletonDemo04() {
            user = new User();
        }

        public User getInstance() {
            return user;
        }
    }
}

解决线程安全问题

使用双重检测锁

public class LazySingletonDemo1 {

    private static LazySingletonDemo1 instance = null;
    public static LazySingletonDemo1 getInstance() {
        if (instance == null) {
            synchronized (LazySingletonDemo1.class) {
                if (instance == null) {
                    instance = new LazySingletonDemo1();
                }
            }
        }
        return instance;
    }
}

这里使用双重检测,是为了防止,当实例存在的时候,不在走同步锁,减小使用锁带来的性能的消耗。

单例模式必定能保证只有一个实例对象吗?

答案是:不能

破坏单例的两种方式:

  1. 反射
  2. 反序列化

1. 反射破坏

经过反射是能够破坏单例的,例如使用内部类实现的单例。经过反射获取其默认的构造函数,而后使默认构造函数可访问,就能够建立新的对象了。

/**
 * @Author 刘翊扬
 * @Version 1.0
 */
public class ReflectionDestroySingleton {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        SingletonLazy instance = SingletonLazy.getInstance();
        Class aClass = SingletonLazy.class;
        // 获取默认的构造方法
        Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
        // 使默认构造方法可访问
        declaredConstructor.setAccessible(true);
        // 建立对象
        SingletonLazy instance2 = declaredConstructor.newInstance();
        System.out.println(instance == instance2);  // 结果是:false
    }
}

怎么阻止???
能够增长一个标志位,用来判断构造函数是否被调用了。

public class SingletonLazy {

    // 标志位
    private static Boolean isNew = false;

    private static SingletonLazy instance;

    private SingletonLazy() {
        synchronized (SingletonLazy.class) {
            if (!isNew) {
                isNew = true;
            } else {
                throw new RuntimeException("单例模式被破坏!");
            }
        }
    }

    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

再次运行:

注意:
增长标志位的确能阻止单例的破坏,可是这个代码有一个BUG,那就是若是单例是先用的反射建立的,那若是你再用正常的方法getInstance()获取单例,就会报错。由于此时标志位已经标志构造函数被调用过了。这种写法除非你能保证getInstance先于反射执行。

public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class aClass = SingletonLazy.class;
        // 获取默认的构造方法
        Constructor<SingletonLazy> declaredConstructor = aClass.getDeclaredConstructor();
        // 使默认构造方法可访问
        declaredConstructor.setAccessible(true);
        // 建立对象
        SingletonLazy instance2 = declaredConstructor.newInstance();
        System.out.println("反射实例:" + instance2);
        // 再次调用
        SingletonLazy instance = SingletonLazy.getInstance();
        System.out.println(instance == instance2);  // 结果是:false
    }

结果:

反序列化

SingletonLazy要实现Serializable接口

public static void main(String[] args) throws Exception {
        //序列化
        SingletonLazy instance1 = SingletonLazy.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("tempfile.txt"));
        out.writeObject(SingletonLazy.getInstance());

        File file = new File("tempfile.txt");
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        //调用readObject()反序列化
        SingletonLazy instance2 = (SingletonLazy) in.readObject();
        System.out.println(instance1 == instance2); // 结果是:false
    }

原理解释:
反序列化为何能生成新的实例,必须从源码看起。这里分析readObject()里面的调用源码。会发现readObject()方法后进入了readObject0(false)方法。

public final Object readObject()
        throws IOException, ClassNotFoundException
    {
        if (enableOverride) {
            return readObjectOverride();
        }

        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);  //经过debug会发现进入此方法
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

分析readObject0方法,经过debug进入了readOrdinaryObject()方法。

private Object readObject0(Class<?> type, boolean unshared) throws IOException {
        boolean oldMode = bin.getBlockDataMode();
        if (oldMode) {
            int remain = bin.currentBlockRemaining();
            if (remain > 0) {
                throw new OptionalDataException(remain);
            } else if (defaultDataEnd) {
                /*
                 * Fix for 4360508: stream is currently at the end of a field
                 * value block written via default serialization; since there
                 * is no terminating TC_ENDBLOCKDATA tag, simulate
                 * end-of-custom-data behavior explicitly.
                 */
                throw new OptionalDataException(true);
            }
            bin.setBlockDataMode(false);
        }

        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                .... 省略部分源码
    
                case TC_OBJECT:
                    if (type == String.class) {
                        throw new ClassCastException("Cannot cast an object to java.lang.String");
                    }
                    return checkResolve(readOrdinaryObject(unshared)); // 经过debug发现进入到了readOrdinaryObject()方法。

               .... 省略部分源码
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

经过分析,readOrdinaryObject()中有两处关键代码,其中关键代码1中的关键语句为:

此处代码是经过描述对象desc,先判断类是否能够实例化,若是能够实例化,则执行desc.newInstance()经过反射实例化类,不然返回null。

obj = desc.isInstantiable() ? desc.newInstance() : null;

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
          // 关键代码=========
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        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 object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

经过断点调试,发现其调用了desc.newInstance()方法。

咱们知道调用newInstance()方法,必定会走类的无参构造方法,可是上面经过debug咱们发现,cons的类型是Object类型,因此,这里面应该是调用类Object的无参构造方法,而不是SingletonLazy类的无参构造

那么怎么改造呢????

继续debug调试:查看readOrdinaryObject()方法

发现,desc.hasReadResolveMethod()这个方法返回的false,因此致使没有执行if条件下面的语句。

desc.hasReadResolveMethod() // 从方法名能够看到,这个方法的名字是检查desc这个(SingleLazy)对象有没有readResolve()方法。

咱们如今阻止破坏单例,应该只须要在SingleLazy类中,实现本身的readResolve()方法便可。

public Object readResolve() {
        return instance;
    }

如今咱们在看看运行的结果:为true

大功告成。。。。

相关文章
相关标签/搜索