woieha320r的博客

单例

确保一个class永远只有一个实例,对外开放一个唯一的获取入口
在Java中能破坏此定义的方式和解决方案如下
· 构造方法:通过私有化构造方法,但外界仍可通过反射调用,可通过if(null != instance)抛异常
· 反序列化:通过定义一个private Object readResolve()返回自己的单例,Java反序列化时会判断如果存在此方法则反射调用它获得实例而不新建实例

饿汉式

· 不论有没有地方用,都实例化,这种办法浪费内存

静态字段实现

· 通过将实例对象赋给静态字段使其在class加载时就实例化

· 没有线程安全问题,如果多个线程同时导致Class加载,由JVM负责线程安全,使其在同class加载器下只加载一次

· 如果没有地方调用getInstance(),而只是比如说外界使用了其静态字段而导致其初始化,会浪费内存

public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
        if (null != INSTANCE) {
            throw new RuntimeException("don't call by reflect");
        }
    }

    private Object readResolve() {
        return getInstance();
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

枚举实现

· 依靠Java的枚举语法,天然就是单例并且不可反射创建

public enum EnumRegisterSingleton {
    INSTANCE;

    private Object readResolve() {
        return getInstance();
    }

    public static EnumRegisterSingleton getInstance() {
        return INSTANCE;
    }
}

懒汉式

· 真的被调用了getInstance()时才实例化

线程不安全写法

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        if (null != instance) {
            throw new RuntimeException("don't call by reflect");
        }
    }

    private Object readResolve() {
        return getInstance();
    }

    public static LazySingleton getInstance() {
        // 线程1未new完的时候,对于线程2来说instance未为null,但返回的实例地址不一定是不同的,取决于实际的线程调度情况
        if (null == instance) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

线程安全写法:双检锁

public class LazySingleton {
    // volatile防止指令重排序详见https://github.com/woieha320r/java-note的线程篇
    private static volatile LazySingleton instance;

    private LazySingleton() {
        if (null != instance) {
            throw new RuntimeException("don't call by reflect");
        }
    }

    private Object readResolve() {
        return getInstance();
    }

    public static LazySingleton getInstance() {
        // 让判断和new原子化,这样线程1在new的时候,线程2不能判断条件,当线程1释放锁,对线程2来说instance已产生
        // 但是这几乎和整个方法都原子没啥区别,每次调用都要竞争锁,得到锁后才能知道instance是否已产生
        /*
        synchronized (this) {
            if (null == instance) {
                instance = new LazySingleton();
            }
        }
        */
        // 在竞争锁前再加一个if,这样只有在instance创建时才会出现锁竞争,而它只被创建一次
        if (null == instance) {
            synchronized (LazySingleton.class) {
                if (null == instance) {
                    // 如果new这个过程发生了指令重排序,"向引用赋值"先于"实例初始化"执行,那么其他线程的第一层if失败,直接返回了一个未被
                    // 初始化的实例地址,外界通过这个地址取值的时候是错误的,需要把instance声明为volatile来阻止"对其赋值后的代码"发生指
                    // 令重排序,详见java-note仓库的线程篇
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

线程安全写法:内部类

· 通过定义私有内部类,只有外界试图获取实例时才由外部类这个唯一调用途径调用内部类,内部类这时才被加载而导致实例化单例

public class InnerClassSingleton {

    private static class Inner {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }

    private Object readResolve() {
        return getInstance();
    }

    public static InnerClassSingleton getInstance() {
        return Inner.INSTANCE;
    }
}

多单例管理

· 把class绑定其唯一的实例放到容器中,外界通过class获取它的唯一实例

Map实现

· ⚠️TODO:原本懒汉中的指令重排序问题,对应现在的:在put过程中是否有可能key比value先写入而导致其他线程取到null?网上例子用的是 ConcurrentHashMap,它对put/remove是线程安全的,那只能保证多线程增删不错乱,跟这个问题好像没有关系

import java.util.HashMap;
import java.util.Map;

public class MapRegisterSingleton {
    private static final Map<Class<?>, Object> CONTAINER = new HashMap<>();

    public static Object getInstance(Class<?> clazz) throws IllegalAccessException, InstantiationException {
        if (!CONTAINER.containsKey(clazz)) {
            synchronized (MapRegisterSingleton.class) {
                if (!CONTAINER.containsKey(clazz)) {
                    CONTAINER.put(clazz, clazz.newInstance());
                }
            }
        }
        return CONTAINER.get(clazz);
    }
}

线程内单例

· 同一个class只在一个线程内有一个唯一实例,不同线程是不同的实例

ThreadLocal实现

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> INSTANCE = ThreadLocal.withInitial(ThreadLocalSingleton::new);

    private ThreadLocalSingleton() {
        if (null != INSTANCE) {
            throw new RuntimeException("don't call by reflect");
        }
    }

    private Object readResolve() {
        return getInstance();
    }

    public static ThreadLocalSingleton getInstance() {
        return INSTANCE.get();
    }
}