单例模式

--什么是单例模式?
--使用单例的应用场景?
--有哪几种单例模式?优缺点?
--单例模式的选择

定义

指一个类只有一个实例,且该类能自行创建这个实例的一种模式。

应用场景

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。

五种单例模式

1.饿汉式

类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。

public class HungrySingleton {

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

以空间换时间,线程安全。

2.懒汉式

类加载时没有生成单例,只有当第一次调用getInstance方法时才去创建这个单例。

public class LazySingleton {
    private static volatile LazySingleton instance = null;
    private LazySingleton() {}  //private 避免类在外部被实例化
    //getInstance方法前加同步
    public static synchronized LazySingleton getInstance(){
        if(instance == null ){
            instance = new LazySingleton();
        }
        return instance;
    }
}

3.双重检查锁

public class LazySingleton {
    private static volatile LazySingleton instance = null;
    private LazySingleton() {}  //private 避免类在外部被实例化
    public static LazySingleton getInstance2(){
        if(instance == null){
            synchronized (LazySingleton.class){
                //为啥不能锁instance
                if (instance == null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

volatile关键字在这里的用处是禁止重排序,在new一个对象的时候JVM分了三个步骤:

  • 分配内存空间。
  • 初始化对象。
  • 将内存空间的地址赋值给对应的引用。

步骤2和3的顺序是乱序执行的,也就是有可能是23或32。在多线程环境下,假如线程A执行了32的顺序,就有可能暴露了一个还没有初始化的对象给其他线程。

4.静态内部类模式

public class LazySingleton {
    private static class LazySingletonHolder {
        public static LazySingleton lazySingleton = new LazySingleton();
    }

    public static LazySingleton getInstance3(){
        return LazySingletonHolder.lazySingleton;
    }
}

优点:可以避免双重校验锁的同步的开销,而且也是线程安全
缺陷:静态内部类无法传参

5.枚举式

public enum EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance() {
        return INSTANCE;
    }
}

优点:可以阻止防序列化和反射攻击,在考虑到以上两种情况时,选择枚举式

调试

    
    public static void main(String[] args) {
        for(int i = 0; i < 200; i++){
            new Thread(() ->
                    System.out.println(Thread.currentThread().getName() + ":" +
                            EnumSingleton.INSTANCE.hashCode())).start();
        }
    }

参考:
happens-before原则 https://www.jianshu.com/p/b9186dbebe8e , https://blog.csdn.net/u011116672/article/details/50147911
DCL问题解决 https://www.jianshu.com/p/ca19c22e02f4
延迟初始化 https://www.infoq.cn/article/double-checked-locking-with-delay-initialization/
为啥能阻止反序列化和防止反射攻击 https://www.cnblogs.com/chiclee/p/9097772.html

posted @ 2019-11-18 16:19  cilieyes  阅读(110)  评论(0编辑  收藏  举报