单例模式属于创建型模式,用于在系统运行过程中保证只有一个实例。
创建单例面临的问题有:反射破坏单例,多线程并发破坏、序列化破坏单例
1 饿汉式单例
/**
* 懒汉式单例
* 已解决反射破坏、序列化问题
* 类加载时就创建了单例,不存在线程安全问题
*/
public class HungrySingleton {
// 类加载时就初始化
private static HungrySingleton hungrySingleton = new HungrySingleton();
// 构造方法私有化
private HungrySingleton(){
// 防止反射破坏单例
if (hungrySingleton != null) {
throw new RuntimeException("非法访问");
}
}
// 全局访问点
public static HungrySingleton getInstance() {
return hungrySingleton;
}
// 防止反序列化破环单例,ObjectInputStream桥接的方法。在反序列化时会调用readResolve方法返回单例
public HungrySingleton readResolve() {
return hungrySingleton;
}
}
2 懒汉式单例
/**
* 懒汉式单例
* 双重检查解决并发问题
* 可以被反射破坏、反序列化破坏
*/
public class LazySingleton {
// 加volatile,防止指令重排序问题。保证线程间可见性
private volatile static LazySingleton lazySingleton;
// 构造方法私有化
private LazySingleton(){}
// 全局访问点
private static LazySingleton getInstance() {
if (lazySingleton == null) {
synchronized (LazySingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
/**
* 懒汉式单例,静态内部类加载的方式
* 是线程安全的
* 可以被反射破坏、反序列化破坏
*/
public class LazyInnerSingleton {
// 构造方法私有化
private LazyInnerSingleton(){}
public static LazyInnerSingleton getInstance() {
return LazyInner.LAZY_INNER_SINGLETON;
}
// 静态内部类在使用时再加载,未使用时不浪费内存
private static class LazyInner {
private static final LazyInnerSingleton LAZY_INNER_SINGLETON = new LazyInnerSingleton();
}
}
3 容器式单例
枚举式单例、注册式单例、线程隔离的单例
3.1 枚举式单例
Jdk提供的Enum类型
/**
* 枚举式单例
* 容器式,但在类装载时就初始化占用内存。是线程安全的
* 不可被反射破坏,JDK层禁止反射创建枚举类型对象。
* 不可被反序列化破坏,JDK底层限制can't deserialize enum
*/
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
3.2 注册式单例
spring的利用ioc容器存储单例的形式
/**
* spring容器式单例
* 在类getInstance使用时才装载到内存中。
*
* 反射破坏,容器本身会被反射破坏,必要时采取保护措施
* 反序列化破坏,容器本身会被反射序列化破坏,必要时采取保护措施
* Map中只会保存一个类的实例,但不是绝对场景的线程安全。当第一次初始化时单例时两个线程同时进入就可能存在put被覆盖。必要时采取保护措施
*/
public class IocSingleton {
// 初始化一个容器,用于装载各种类的单例
private static Map<String, Object> ioc = new HashMap<>();
// 构造方法私有化
private IocSingleton(){}
// 根据需要的对象类型,获取对应的实例
public static Object getInstance(Class clazz) {
String name = clazz.getName();
if (!ioc.containsKey(name)) {
Constructor constructor = clazz.getConstructor(null);
constructor.setAccessible(true);
Object o = constructor.newInstance();
ioc.put(name, o);
}
return ioc.get(name);
}
}
3.3 线程隔离式单例
利用ThreadLocal类为每个线程提供线程间相互隔离的单例
/**
* 线程隔离的单例。
* 容器式单例,一个线程创建一个单例
* 该场景内是线程安全的。
* 反射破坏,容器本身会被反射破坏,必要时采取保护措施
* 反序列化破坏,容器本身会被反射序列化破坏,必要时采取保护措施
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocal
= new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance() {
return threadLocal.get();
}
}
本文详细介绍了Java中的单例设计模式,包括饿汉式、懒汉式、静态内部类加载、枚举式、注册式(Spring容器)和线程隔离式(ThreadLocal)单例实现方式。每种方式的线程安全性、内存占用和破坏单例的可能途径都有所讨论。枚举式单例被指出是线程安全且无法通过反射和序列化破坏的。
321

被折叠的 条评论
为什么被折叠?



