23种设计模式-单例模(java)

88 阅读3分钟

定义:

1.顾名思义单例模式就是一个类只提供一个实例对象给外部访问。确保全局使用该类所对应的对象永远都是一个对象。

应用场景:

1.各种对象工厂
2.各种管理器

实现方案:

1.(推荐使用)饿汉式
    通过提前创建对象,将对象唯一化,提供给外部访问的只有已创建的对象的返回的方法,同时屏蔽掉外部构建新对象的手段。
    他的线程安全性是由jvm保证的。(jvm 会保证类只会被load到内存一次,而静态对象的初始化也是在load的过程中的.
    所以jvm初始化静态对象时只会初始化一次,保证了线程的安全性)。
    但是她的对象会直接创建不管有没有使用(缺点)
public class Factory {
    private static final Factory factory = new Factory();

    private Factory() {
    }
    public static Factory getInstance(){
        return factory;
    }

    public static void main(String[] args) {
        Factory instance = Factory.getInstance();
        Factory instance1 = Factory.getInstance();
        System.out.println(instance == instance1);//true
    }
}
2.懒汉式
    与饿汉式相比,懒汉式是当外部有调用的时候,才会去构建对象,也就是我们说的懒加载。
    
public class Factory2 {
    private static Factory2 factory;

    private Factory2() {
    }
    public static Factory2 getInstance(){
        if(factory == null){

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {

                
            }
            factory  = new Factory2();

        }
        return factory;
    }

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

此种方案有安全性问题,当多线程访问时,该实例对象可能不是同一个对象,睡眠时间是为了模拟对象的构建逻辑,更明显的观察出安全性问题。

安全性问题可以通过加锁的方式进行解决,比如在获取对象的方法上加锁

public class Factory3 {
    private static Factory3 factory;

    private Factory3() {
    }
    public static synchronized Factory3 getInstance(){
        if(factory == null){

            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {


            }
            factory  = new Factory3();

        }
        return factory;
    }

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

但是,该方法也会面临锁的粒度过大,导致性能降低,为了更好的使用,通过降低锁粒度的方式,提升效率。

public class Factory4 {
    private static Factory4 factory;

    private Factory4() {
    }
    public static  Factory4 getInstance(){
        if(factory == null){
            synchronized (Factory4.class) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
                factory = new Factory4();
            }

        }
        return factory;
    }

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

该方案虽然降低了锁的力度,但是没法保证单例的问题,因为当线程持有锁的时候,可能有多个线程竞争,当进程的竞争随着前面锁的释放,会继续执行,导致对象被多次创建。因此引出了创建单的dcl方法(双重检查),解决上一个步骤中竞争锁的线程会多次创建对象,同时为了解决指令重排问题,加上volatile,解决对象没有初始化的时候就已经将内存地址指向了该引用

public class Factory5 {
    private volatile static Factory5 factory;

    private Factory5() {
    }
    public static Factory5 getInstance(){
        if(factory == null){
            synchronized (Factory5.class) {
                if(factory != null){
                    return factory;
                }
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
                factory = new Factory5();
            }

        }
        return factory;
    }

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

至此关于单例模式的一些讲解到这已经结束了,在实际开发中还是建议使用饿汉式,因为它简单明了。对于内存而言,现在不是早期的时候,代价没那么高,也没有那么紧张。

延伸另外两种其他写法1:通过静态内部类,2:使用枚举

public class Factory6 {
    private Factory6() {
    }
    public static class FactMa{
        private static final Factory6 factory = new Factory6();

    }

    public static Factory6 getInstance(){

        return FactMa.factory;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Factory5.getInstance())).start();
        }
    }
}
public enum Factory7 {
    INSTANCE;
    public void m(){};

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Factory7.INSTANCE.hashCode())).start();
        }
    }
}