单例模式

单例模式

  1. 饿汉式:构造器私有化,直接初始化对象,调用getInstance方法获取单例对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MySingleton1 {
    private static final MySingleton1 INSTANCE = new MySingleton1();

    private MySingleton1() {};

    public static MySingleton1 getInstance() {
    return INSTANCE;
    }
    }
  2. 懒汉式:相较于饿汉式带来的好处是使用对象时才会去初始化对象,但是同时也带来了线程安全问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MySingleton2 {
    private static MySingleton2 INSTANCE;

    private MySingleton2() {
    }

    public static MySingleton2 getInstance() {
    if (INSTANCE == null) {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    INSTANCE = new MySingleton2();
    }
    return INSTANCE;
    }
    }
  3. 双重检查单例:在一定程度上解决了懒汉式的线程安全问题,但是依然存在指令重排序带来的对象半初始化问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class MySingleton3 {
    private static volatile MySingleton3 INSTANCE; //JIT

    private MySingleton3() {
    }

    public static MySingleton3 getInstance() {
    if (INSTANCE == null) {
    //双重检查
    synchronized (MySingleton3.class) {
    if(INSTANCE == null) {
    try {
    Thread.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    INSTANCE = new MySingleton3();
    }
    }
    }
    return INSTANCE;
    }
    }
  4. 静态内部类单例:加载外部类时不会加载静态内部类,这样可以实现懒加载,且JVM保证了静态内部类只会加载一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class MySingleton4 {

    private MySingleton4() {
    }

    private static class Mgr07Holder {
    private final static MySingleton4 INSTANCE = new MySingleton4();
    }

    public static MySingleton4 getInstance() {
    return Mgr07Holder.INSTANCE;
    }
    }
  5. 枚举单例:枚举类经过反编译之后,其实它的枚举项都是static属性,在java的类加载机制中,在单例对象类被正真使用到的时候,会在对象创建过程中的linking阶段的Preparation阶段为静态变量赋默认值,在initialization阶段为对象赋初始值,而java类加载机制保证单例对象类只会被加载一次且类加载过程是线程安全的,枚举是没有构造方法的,所以不会在反序列化过程中通过反射重新new对象而导致单例被破坏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public enum MySingleton5 {

    INSTANCE;

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

    }

总结:单例模式在实际生产中很少手动去实现,通常情况下我们会通过Spring来保证我们对象的单例,通过Spring的Bean工厂来获取单例对象