单例模式,你学到什么阶段了?

饿汉式

不考虑反射问题

public class Hungry {

    private Hungry() {

    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    
    }
}

存在的问题:由于在未使用前就创建了对象,所以会比较消耗内存

多线程创建测试:

package com.eh.single;

public class Hungry {
    private Hungry() {

    }
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }

    public static void main(String[] args) {
        new Thread(() -> {
            Hungry instance = Hungry.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(() -> {
            Hungry instance = Hungry.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(() -> {
            Hungry instance = Hungry.getInstance();
            System.out.println(instance);
        }).start();
        new Thread(() -> {
            Hungry instance = Hungry.getInstance();
            System.out.println(instance);
        }).start();
    }
}

运行结果:

可以发现四个线程同时创建的对象都是一样的,因此饿汉式不存在多线程问题

懒汉式

不考虑多线程、反射问题写法

public class LazyMan {
    private static LazyMan lazyMan;
    
    private LazyMan() { }

    public static LazyMan getInstance() {
        lazyMan = new LazyMan();
        return lazyMan;
    }
}

多线程测试:

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    public static LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan instance = LazyMan.getInstance();
                System.out.println(instance);
            }).start();
        }
    }
}

运行截图:

可以发现多个同时创建的懒汉对象不一样,所以存在多线程问题

多线程问题改进

public class LazyMan {
    private static LazyMan lazyMan;

    private LazyMan() {}

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

多线程问题改进后,还存在指令重排问题,即lazyMan = new LazyMan();并不一定是按照我们所想的顺序执行,需要避免指令重排

避免指令重排问题改进

volatile可以避免指令重排

package com.eh.single;

public class LazyMan {
    private volatile static LazyMan lazyMan;

    private LazyMan() {}

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

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan instance = LazyMan.getInstance();
                System.out.println(instance);
            }).start();
        }
    }
}

此时改进后的懒汉模式被称之为DCL懒汉式

但是此时的懒汉式还存在问题,即外部可以通过反射的方式创建多个对象

需要再次改进(添加一个标记用于记录LazyMan是否创建)

package com.eh.single;

public class LazyMan {
    // 用于标记LazyMan是否创建
    private static boolean flag = false;

    private volatile static LazyMan lazyMan;

    private LazyMan() {
        synchronized (LazyMan.class) {
            if (flag) {
                throw new RuntimeException("不要多次创建");
            } else {
                flag = true;
            }
        }
    }

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

此时的单例模式就相对比较安全,如果想要更安全就需要使用到枚举类,这里就不多赘述...

posted @ 2021-03-06 16:28  EhuoWeirdo  阅读(47)  评论(0编辑  收藏  举报