java设计模式精讲 Debug 方式+内存分析 第8章 单例模式

单例模式

  • 8-1 单例模式讲解
  • 8-2 单例设计模式-懒汉式及多线程Debug实战
  • 8-3 单例设计模式-DoubleCheck双重检查实战及原理解析
  • 8-4 单例设计模式-静态内部类-基于类初始化的延迟加载解决方案及原理解析
  • 8-5 单例设计模式-饿汉式
  • 8-6 单例设计模式-序列化破坏单例模式原理解析及解决方案
  • 8-7 单例设计模式-反射攻击解决方案及原理分析
  • 8-8 单例设计模式-Enum枚举单例、原理源码解析以及反编译实战
  • 8-9 单例设计模式-容器单例
  • 8-10 单例设计模式-ThreadLocal线程单例
  • 8-11 单例模式源码分析(jdk+spring+mybatis)

8-1 单例模式讲解

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第1张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第2张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第3张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第4张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第5张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第6张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第7张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第8张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第9张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第10张图片


8-2 单例设计模式-懒汉式及多线程Debug实战

懒汉式单例:

public class LazySingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private static LazySingleton lazySingleton = null;
    /** 构造器要进行私有化 */
    private LazySingleton(){

    }
    public static LazySingleton getInstance() {
        /** 这个是有线程安全的问题 */
        if (lazySingleton == null) {
            /**
             * 如果一个线程进来了,但是这个时候,在new实例的时候,阻塞了或者还没有new出实例,
             * 这个时候,另外一个线程判断lazySingleton依然是空的,那么就这时候,也进来了,
             * 那么这个时候,就是有线程安全问题的
             */
            lazySingleton =  new LazySingleton();
        }
        return lazySingleton;
    }
}


我们来测试一下:

public class Test {
    public static void main(String[]args){
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(lazySingleton);
    }
}

debug调试:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第11张图片


我们再来看看多线程的时候,会出现什么问题:

public class T implements Runnable{
    @Override
    public void run() {
        LazySingleton lazySingleton = LazySingleton.getInstance();
        System.out.println(Thread.currentThread().getName()+" "+lazySingleton);
    }
}

测试代码如下:

public class Test {
    public static void main(String[]args){
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}

program end
Thread-1 com.ldc.design.pattern.creational.singleton.LazySingleton@5102a646
Thread-0 com.ldc.design.pattern.creational.singleton.LazySingleton@5102a646


这个时候,我们就要用到多线程debug来进行调试:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第12张图片


模拟两个线程,一个线程在进入if之后,还没有new出实例, 这个时候,另外一个线程也进来了,if判断这个时候还没有实例,于是也进入了if里面,这个时候,就new出来两个实例:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第13张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第14张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第15张图片


然后,我们在切换到thread0,让它赋值上:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第16张图片


接着我们切换到Thread1:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第17张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第18张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第19张图片


我们接着向下执行:
Thread0和Thread1都放过:
虽然此时,两个对象还是同一个对象,但是是经过了修改了的:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第20张图片


我们再debug返回不同的对象:
我们先都两个线程进入if判断,然后一个线程直接执行完成,再另外一个线程执行完成,这个时候,返回的就是两个对象了:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第21张图片


我们对懒汉式单例模式的线程安全问题有几种解决方案:
方式一:在获取实例的方法上添加synchronized关键字
如果锁加载静态方法上 ,那么就相当于锁是加在这个类的class文件;如果不是静态方法相当于是在堆内存中生成的对象:

public class LazySingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private static LazySingleton lazySingleton = null;
    /** 构造器要进行私有化 */
    private LazySingleton(){

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

方式二:把获取实例的方法内添加synchronized代码块

public class LazySingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private static LazySingleton lazySingleton = null;
    /** 构造器要进行私有化 */
    private LazySingleton(){

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

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第22张图片


我们再运行Thread1,发现运行不下去了,已经是阻塞的状态了:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第23张图片


最后拿的就是同一个对象:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第24张图片
但是,我们知道,加锁和解锁的时候,是会带来额外的开销,对性能会有一定的影响;我们再来进行演进,在性能和安全上进行平衡;


8-3 单例设计模式-DoubleCheck双重检查实战及原理解析

我们 可以这样来写:

public class LazyDoubleCheckSingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    /** 构造器要进行私有化 */
    private LazyDoubleCheckSingleton(){

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

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第25张图片


这个时候,会有一个风险:那就是发生了重排序:

public class LazyDoubleCheckSingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    /** 构造器要进行私有化 */
    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance() {
        /** 如果2和3进行重排序,那么这里的判断并不为空,这个时候,实际上对象还没有初始化好,就可以进行这个判断 */
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    /**
                     * 实际上有三个步骤:
                     * 1. 分配内存给这个对象
                     * 2. 初始化对象
                     * 3.设置lazyDoubleCheckSingleton指向刚分配的内存地址
                     * 2和3的顺序有可能会被颠倒,
                     *
                     * 这个时候,就规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定
                     * 它保证了重排序不会改变单线程内的程序执行结果
                     */
                    lazyDoubleCheckSingleton =  new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

下图就是表示单线程的情况:规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定,它保证了重排序不会改变单线程内的程序执行结果。
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第26张图片


现在来看一下多线程的情况:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第27张图片


我们可以尝试不允许重排序:
我们在初始化的时候,给它加上一个volatile关键字:这个时候,就可以实现线程安全的延迟初始化,这样的话,重排序就是会被禁止,在多线程的时候,CPU也有共享内存,我们加上了这个关键字了之后,所有线程就能看到共享内存的最新状态,保证了内存的可见性,使用volatile的时候,在进行写操作的时候,会多出一些汇编代码,起到两个作用1)将当前处理器缓存好的数据写回到系统内存中,其他内存从共享内存中同步数据,这样的话,就保证了共享内存的可见性,这里就是使用了缓存一致性的协议,当发现缓存内存中的数据无效,会重新从系统内存中把数据读回处理器的内存里;

public class LazyDoubleCheckSingleton {
    /** 懒汉模式的话,开始没有进行初始化 */
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    /** 构造器要进行私有化 */
    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance() {
        /** 如果2和3进行重排序,那么这里的判断并不为空,这个时候,实际上对象还没有初始化好,就可以进行这个判断 */
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    /**
                     * 实际上有三个步骤:
                     * 1. 分配内存给这个对象
                     * 2. 初始化对象
                     * 3.设置lazyDoubleCheckSingleton指向刚分配的内存地址
                     * 2和3的顺序有可能会被颠倒,
                     *
                     * 这个时候,就规定所有的线程在执行java程序的时候,必须要遵守intra-thread semantics这么一个规定
                     * 它保证了重排序不会改变单线程内的程序执行结果
                     */
                    lazyDoubleCheckSingleton =  new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

通过volatile和doubleCheck的这种方式既兼顾了性能,又兼顾了线程安全的问题;


我们来进行测试一下:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第28张图片


这个时候,拿到这个就是同一个对象:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第29张图片

8-4 单例设计模式-静态内部类-基于类初始化的延迟加载解决方案及原理解析


这个就是用静态内部类来实现单例模式:

public class StaticInnerClassSingleton {
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){

    }
}

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第30张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第31张图片

8-5 单例设计模式-饿汉式

在类加载的时候,就完成了实例化,避免了线程同步的问题,缺点就是在类加载的时候,就完成了初始化,没有延迟加载,这个时候,就是会造成内存的浪费

public class HungrySingleton {
    private final static HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton() {

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

也可以这样来写:

public class HungrySingleton {
    private final static HungrySingleton hungrySingleton;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {

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

饿汉式和懒汉式最大的区别就是在有没有延迟加载;


8-6 单例设计模式-序列化破坏单例模式原理解析及解决方案

我们给这类实现一个序列化接口:

public class HungrySingleton implements Serializable {
    private final static HungrySingleton hungrySingleton;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {

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

我们来测试:

public class Test {
    public static void main(String[]args) throws IOException, ClassNotFoundException {
        HungrySingleton instance = HungrySingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
        oos.writeObject(instance);

        File file = new File("singleton_file");
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        HungrySingleton newInstance = (HungrySingleton) ois.readObject();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

输出结果:

com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
com.ldc.design.pattern.creational.singleton.HungrySingleton@443b7951
false


这个时候,通过序列化和反序列化拿了不同的对象;而我们希望拿到的是同一的对象,我们可以这样来做:

public class HungrySingleton implements Serializable {
    private final static HungrySingleton hungrySingleton;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {

    }
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
    /** 我们加上这样的一个方法 */
    private Object readResolve() {
        return hungrySingleton;
    }
}

我们再来进行测试,这个时候,两个对象就是同一个对象:

com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
com.ldc.design.pattern.creational.singleton.HungrySingleton@312b1dae
true


我们通过用debug的方式查看源码,我们可以看出这个方法是通过反射出来的:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第32张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第33张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第34张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第35张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第36张图片
一旦,我们在程序中使用了序列化的时候,一定要考虑序列化对单例破坏;


8-7 单例设计模式-反射攻击解决方案及原理分析


我们用反射来写:

public class Test {
    public static void main(String[]args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance==newInstance);
    }
}

测试结果:

com.ldc.design.pattern.creational.singleton.HungrySingleton@12edcd21
com.ldc.design.pattern.creational.singleton.HungrySingleton@34c45dca
false


现在,我们就来写反射防御的代码:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第37张图片
在用静态内部类来生成的也可以用上这个反射防御的方式:

public class StaticInnerClassSingleton {
    private static class InnerClass {
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){
        if (InnerClass.staticInnerClassSingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
}

8-8 单例设计模式-Enum枚举单例、原理源码解析以及反编译实战


public enum  EnumInstance {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第38张图片


我们要调用方法的话,那么我们就是可以这样来写:

public enum  EnumInstance {
    INSTANCE {
        @Override
        protected void printTest() {
            System.out.println("Geely Print Test");
        }
    };
    protected abstract void printTest();

    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
    public static EnumInstance getInstance() {
        return INSTANCE;
    }
}

8-9 单例设计模式-容器单例

我们可以这样来写:

public class ContainerSingleton {
    private static Map<String, Object> singletonMap = new HashMap<>();
    public static void putInstance(String key,Object instance) {
        if (StringUtils.isNotBlank(key) && instance!=null) {
            if (!singletonMap.containsKey(key)) {
                singletonMap.put(key, instance);
            }
        }
    }

    public static Object getInstance(String key) {
        return singletonMap.get(key);
    }

    private ContainerSingleton() {

    }
}

我们来测试一下:

public class Test {
    public static void main(String[]args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Thread t1 = new Thread(new T());
        Thread t2 = new Thread(new T());
        t1.start();
        t2.start();
        System.out.println("program end");
    }
}

如果map使用了HashTable,那么它就是线程安全的,但是这样的话,性能会有降低;


8-10 单例设计模式-ThreadLocal线程单例

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第39张图片


8-11 单例模式源码分析(jdk+spring+mybatis)

java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第40张图片


懒汉式:
java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第41张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第42张图片


java设计模式精讲 Debug 方式+内存分析 第8章 单例模式_第43张图片

你可能感兴趣的:(Java设计模式精讲)