设计模式-单例模式

1、概念

创建型模式

确保一个类在任何情况下都绝对只有一个实例,并且提供一个全局访问点。

2、模式

1)饿汉式单例模式

在类加载的时候就立即初始化,并且创建单例对象。

绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题

//标准代码
public class HungrySingleton {

    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}


//静态代码块
public class HungryStaticSingleton {

    private static final HungryStaticSingleton hungrySingleton;

    static {
        hungrySingleton = new HungryStaticSingleton();
    }

    private HungryStaticSingleton() {
    }

    public static HungryStaticSingleton getInstance() {
        return hungrySingleton;
    }
}

适用:

单例对象较少的情况,可以保证线程安全,执行效率高

缺点:

所有对象类加载的时候就实例化,如果系统中有大批量的单例对象存在,那么系统初始化导致大量的内存浪费

2)懒汉式单例

单例对象要在被使用的时候才会初始化

public class LazySimpleSingleton {
    
    private LazySimpleSingleton() {}
    
    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
        if(lazy == null){
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
    
}

导致一个新的问题,如果在多线程环境下,会出现线程安全问题

public class ExectorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + instance);
    }
}

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new ExectorThread());
        Thread thread1 = new Thread(new ExectorThread());
        thread.start();
        thread1.start();
        System.out.println("END");
    }
}

设计模式-单例模式_第1张图片

DEBUG多线程环境下,可以看到LazySimpleSingleton被实例化了两次,可能会出现打印结果一致的情况,实际也是后面执行的线程覆盖了前者的结果。

public class LazySingleton {

    private LazySingleton() {}

    private static LazySingleton lazy = null;

    public synchronized static LazySingleton getInstance() {
        if(lazy == null){
            lazy = new LazySingleton();
        }
        return lazy;
    }

}

加上synchronized关键字加锁,如果在线程数量较多的情况下,如果CPU分配压力上升,会导致大批线程阻塞,导致程序性能下降。

public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton() {
    }

    public static LazyDoubleCheckSingleton getInstance() {
        if (lazy == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazy == null) {
                    lazy = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazy;
    }

}

第一个线程调用getInstance()时,第二个线程也可以调用。当一个线程执行 synchronized 时会上锁,第二个线程会变成MONITOR状态,出现阻塞,阻塞并不是基于整个LazyDoubleCheckSingleton类的上锁,而是在getInstance()内部的阻塞,只要逻辑不是很负责,调用者感受不到阻塞的时间差。

 

3)注册式单例模式(登记式单例模式

将每一个实例都登记到某一个地方,使用唯一的标识获取实例。

public enum EnumSingleton {

    INSTANCE;

    private Object data;

    public void setData(Object data) {
        this.data = data;
    }

    public Object getData() {
        return data;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}
public static void main(String[] args) {
        try {
            EnumSingleton instance = EnumSingleton.getInstance();
            instance.setData(new Object());
            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            EnumSingleton enumSingleton = null;
            enumSingleton = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(instance.getData());
            System.out.println(enumSingleton.getData());
            System.out.println(enumSingleton.getData() == instance.getData());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

设计模式-单例模式_第2张图片

枚举式单例模式在静态代码块中就给INSTANCE进行赋值,是饿汉式单例模式的体现。

看源代码:

设计模式-单例模式_第3张图片

设计模式-单例模式_第4张图片

  •  readEnum():,通过类名和类对象类找到一个唯一的枚举对象。枚举对象不可能被类加载器加载多次。

再看反射是否能破坏单例:

        try{
            Class enumSingletonClass = EnumSingleton.class;
            Constructor c = enumSingletonClass.getDeclaredConstructor();
            c.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }

设计模式-单例模式_第5张图片

 没有找到无参的构造方法

修改代码

设计模式-单例模式_第6张图片

 不能用发射来创建枚举类型

在源码中,newInstance()做了强制性的判断

设计模式-单例模式_第7张图片

 

4)容器式单例

public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map ioc = new ConcurrentHashMap();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object obj = null;
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

适用于需要大量创建单例对象的场景,便于管理。但是是非线程安全的。

5)线程单例


public class ThreadLocalSingleton {
    private static final ThreadLocal threadLocalInstance =
            new ThreadLocal() {
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocalInstance.get();
    }
}
public class test {
    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        Thread thread = new Thread(new ExectorThread());
        Thread thread1 = new Thread(new ExectorThread());
        thread.start();
        thread1.start();
    }
}

设计模式-单例模式_第8张图片

 主线程无论调用多少次,获取到的实例是同一个,都在连个子线程中分别获取到了不同的实例。

ThreadLocal将所有的对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上以空间换时间来实现线程隔离。

3、破坏单例

1)反射破坏单例

        try {
            Class clazz = LazyInnerSingleton.class;
            Constructor constructor = clazz.getDeclaredConstructor(null);
            //强制访问
            constructor.setAccessible(true);
            //强制初始化
            Object o = constructor.newInstance();
            Object o1 = constructor.newInstance();
            System.out.println(o == o1);
        } catch (Exception e) {
            e.printStackTrace();
        }

 强制初始化后,出现两个不同的实例,优化重复创建抛出异常

public class LazyInnerClassSingleton {

    private LazyInnerClassSingleton() {
        //加个判断
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    //默认不加载
    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }

    //static是为了使单例的空间共享,保证方法不会被重写重载
    public static final LazyInnerClassSingleton getInstance() {
        //返回结果前,一定先加载内部类
        return LazyHolder.LAZY;
    }

}

2)序列化破坏单例

单例创建好后,有时候需要将对象序列化再写入磁盘,读取的时候再进行反序列化,转化为内存对象。反序列化后的对象会重新分配内存。

public class SerializableSingleton implements Serializable {

    private SerializableSingleton() {}

    private final static SerializableSingleton INSTANCE = new SerializableSingleton();

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }

}
        SerializableSingleton s1 = null;
        SerializableSingleton s2 = SerializableSingleton.getInstance();
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("SerializableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SerializableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SerializableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
        } catch (Exception e) {
            System.out.println(e);
        }

 反序列化后的对象和手动创建的对象是不同的

优化代码:

public class SerializableSingleton implements Serializable {

    private SerializableSingleton() {}

    private final static SerializableSingleton INSTANCE = new SerializableSingleton();

    public static SerializableSingleton getInstance() {
        return INSTANCE;
    }
    
    private Object readResolve(){
        return INSTANCE;
    }

}

原理:

设计模式-单例模式_第9张图片

设计模式-单例模式_第10张图片

设计模式-单例模式_第11张图片

  •  isInstantiable(),判断构造方法是否为空,不为空返回true,只要有无参构造方法就会实例化。
  • hasReadResolveMethod(),就是通过反射找到一个无参的readResolve()方法并保存。
  • invokeReadResolve(),反射调用readResolveMethod()

虽然readResolve()方法返回实例解决了单例模式被破坏的问题,但实际上是实例化了两次,只不过新创建的对象没有被返回。如果创建对象的动作发生频繁加快,就以为这内存分配开销也会随之增大。

你可能感兴趣的:(设计模式,单例模式)