单例的作用

节省内存和计算
保证结果的正确性
方便管理

单例的适用场景

无状态的工具类
全局信息类

单例的8种写法

饿汉式(静态常量)【可用】

  1. /**
  2. * 描述: 饿汉式(静态常量)(可用)
  3. */
  4. public class Singleton1 {
  5. private final static Singleton1 INSTANCE = new Singleton1();
  6. private Singleton1() {
  7. }
  8. public static Singleton1 getInstance() {
  9. return INSTANCE;
  10. }
  11. }

饿汉式(静态代码块)【可用】

  1. /**
  2. * 描述: 饿汉式(静态代码块)(可用)
  3. */
  4. public class Singleton2 {
  5. private final static Singleton2 INSTANCE;
  6. static {
  7. INSTANCE = new Singleton2();
  8. }
  9. private Singleton2() {
  10. }
  11. public static Singleton2 getInstance() {
  12. return INSTANCE;
  13. }
  14. }

懒汉式(线程不安全)【不可用】

  1. /**
  2. * 描述: 懒汉式(线程不安全)
  3. */
  4. public class Singleton3 {
  5. private static Singleton3 instance;
  6. private Singleton3() {
  7. }
  8. public static Singleton3 getInstance() {
  9. if (instance == null) {
  10. instance = new Singleton3();
  11. }
  12. return instance;
  13. }
  14. }

懒汉式(线程安全,同步方法)【不推荐用】

  1. /**
  2. * 描述: 懒汉式(线程安全)(不推荐)
  3. */
  4. public class Singleton4 {
  5. private static Singleton4 instance;
  6. private Singleton4() {
  7. }
  8. public synchronized static Singleton4 getInstance() {
  9. if (instance == null) {
  10. instance = new Singleton4();
  11. }
  12. return instance;
  13. }
  14. }

懒汉式(线程不安全,同步代码块)【不可用】

  1. /**
  2. * 描述: 懒汉式(线程不安全)(不推荐)
  3. */
  4. public class Singleton5 {
  5. private static Singleton5 instance;
  6. private Singleton5() {
  7. }
  8. public static Singleton5 getInstance() {
  9. if (instance == null) {
  10. synchronized (Singleton5.class) {
  11. instance = new Singleton5();
  12. }
  13. }
  14. return instance;
  15. }
  16. }

懒汉式(双重检查)【推荐】

  1. /**
  2. * 描述: 双重检查(推荐面试使用)
  3. */
  4. public class Singleton6 {
  5. private volatile static Singleton6 instance;
  6. private Singleton6() {
  7. }
  8. public static Singleton6 getInstance() {
  9. if (instance == null) {
  10. synchronized (Singleton6.class) {
  11. if (instance == null) {
  12. instance = new Singleton6();
  13. }
  14. }
  15. }
  16. return instance;
  17. }
  18. }

优点:线程安全;延迟加载;效率较高

为什么要double-check

  1. 线程安全
  2. 单check行不行
  3. 性能问题

    为什么要用volatile

  4. 新建对象有3个步骤,不是原子性的

  5. 重排序会带来空指针问题
  6. 防止重排序

重点解释
“在第一个线程退出synchronized之前,里面的操作执行了一部分,比如执行了new却还没执行构造函数,然后第一个线程被切换走了,这个时候第二个线程刚刚到第一重检查,所以看到的对象就是非空,就跳过了整个synchronized代码块,获取到了这个单例对象,但是使用其中的属性的时候却不是想要的值。”

截屏2020-01-23上午10.42.54.png

静态内部内【推荐用】

  1. /**
  2. * 描述: 静态内部类方式,可用
  3. */
  4. public class Singleton7 {
  5. private Singleton7() {
  6. }
  7. private static class SingletonInstance {
  8. private static final Singleton7 INSTANCE = new Singleton7();
  9. }
  10. public static Singleton7 getInstance() {
  11. return SingletonInstance.INSTANCE;
  12. }
  13. }

枚举【推荐】

  1. /**
  2. * 描述: 枚举单例
  3. */
  4. public enum Singleton8 {
  5. INSTANCE;
  6. public void whatever() {
  7. }
  8. }

不同写法对比

  • 饿汉:简单,但是没有懒加载
  • 懒汉:有线程安全问题
  • 静态内部内:可用
  • 双重检查:面试用
  • 枚举:最好

为什么枚举最好?

  1. 写法简单
  2. 线程安全、懒加载 (枚举是一种特殊类,枚举会被编译成 final class,然后继承枚举这个父类,它的各个实例都是通过static定义的,枚举本质就是静态的对象,在第一使用到枚举实例时,才会加载进来)
  3. 避免反射、反序列化破坏单例