单例模式(Singleton Pattern)
有时候又称“单件模式”或者“单态模式”,是Java中比较常见的创建型设计模式,他的核心是确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。比较经典的例子就是Windows系统中的“回收站”。
如何确保一个类在任何情况下都绝对只有一个实例?是单例模式设计的主要实现方向。下面介绍下单例模式的主要实现方法。
饿汉式实现
在单例类首次加载时就创建实例:
/**
* 饿汉式单例
*
* 不管有没有调用,在类加载时直接实例化单例
*
* 缺点,类加载时就创建实例,浪费空间
*/
public class HungrySingleton {
/**
* 类加载时创建初始化
*/
private static final HungrySingleton INSTANCE = new HungrySingleton();
/**
* 私有化构造方法
*/
private HungrySingleton(){}
/**
* 提供全局访问点
* @return 单例对象
*/
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
优化版的饿汉式单例模式:
/**
* 饿汉式静态单例, 对饿汉式单例的进一步优化
*
* 缺点,类加载时就创建实例,浪费空间
*/
public class HungryStaticSingleton {
/**
* 类加载时创建初始化
*/
private static final HungryStaticSingleton INSTANCE;
/**
* 在静态代码块中初始化
*/
static {
INSTANCE = new HungryStaticSingleton();
}
/**
* 私有化构造方法
*/
private HungryStaticSingleton(){}
/**
* 提供全局访问点
* @return 单例对象
*/
public static HungryStaticSingleton getInstance() {
return INSTANCE;
}
}
序列化单例
/**
* 饿汉式 防止序列化破坏单例
* <p>
*
* 懒汉式序列化破坏单例模式
*
* 缺点,类加载时就创建实例,浪费空间
*/
public class SerializableSingleton implements Serializable {
/**
* 类加载时创建初始化
*/
private static final SerializableSingleton INSTANCE = new SerializableSingleton();
/**
* 私有化构造方法
*/
private SerializableSingleton() {
}
/**
* 提供全局访问点
*
* @return 单例对象
*/
public static SerializableSingleton getInstance() {
return INSTANCE;
}
/**
* 防止破坏单例模式,需新增此方法
* <p>
* 新增readResolve,是为了覆盖反序列化出来的对象
* 整个单例过程中创建了两次,发生在JVM层面,相对来说比较安全
* 之前反序列化的对象会被GC回收掉
*
* @return 单例对象
*/
private Object readResolve() {
return INSTANCE;
}
}
懒汉式实现
/**
* 懒汉式简单单例
* <p>
* 在调用时实例化单例,
* <p>
* 缺点:由于加锁造成的性能问题
*/
public class LazySimpleSingleton {
/**
* 类加载时不初始化实例
*/
private static LazySimpleSingleton INSTANCE;
/**
* 私有化构造方法
*/
private LazySimpleSingleton() {
}
/**
* 等到调用时初始化单例实例
* <p>
* 提供全局访问点
* <p>
* 如果没有synchronized关键字,容易出现线程安全问题,因此需要添加synchronized进行同步,
* 在方法添加synchronized关键字,可能造成整个类都会因为方法上的synchronized关键字被锁,
* 因此一般都会在方法内部添加synchronized关键字
*
* @return 单例对象
*/
public static synchronized LazySimpleSingleton getInstance() {
//这里需要办法保证只有一个实例
if (INSTANCE == null)
INSTANCE = new LazySimpleSingleton();
return INSTANCE;
}
}
双重检查锁单例
/**
* 懒汉式 双重检查锁单例
* <p>
* 为了保证单例的线程安全,使用双重加锁的方式
* 问题:加锁所造成的性能问题
*/
public class LazyDoubleCheckSingleton {
/**
* 类加载时不初始化实例
* <p>
* volatile 可以防止指令重排序问题
*/
private volatile static LazyDoubleCheckSingleton INSTANCE;
/**
* 私有化构造方法
*/
private LazyDoubleCheckSingleton() {
}
/**
* 等到调用时初始化单例实例
* <p>
* 提供全局访问点
* <p>
* 如果没有 synchronized关键字,容易出现线程安全问题,因此需要添加synchronized进行同步
*
* @return 单例对象
*/
public static LazyDoubleCheckSingleton getInstance() {
//这里需保证只有一个实例
if (INSTANCE == null)
synchronized (LazyDoubleCheckSingleton.class) {
if (INSTANCE == null)//防止重复创建
INSTANCE = new LazyDoubleCheckSingleton();
/*
* CPU执行时会转换成JVM指令执行
*
* 1.分配内存给这个对象
* 2.初始化对象
* 3.将初始化后的对象和内存地址建立关联,赋值
* 4.用户初次访问
*
*/
//指令重排序问题, 所以会在单例对象添加volatile修饰
}
return INSTANCE;
}
}
懒汉式+饿汉式性能最优的单例写法
/**
* 懒汉式 内部类单例
*
* 内部使用内部类的饿汉式单例,来避免线程安全问题,
* 外部使用懒汉式单例,来减少内存开销
*
* 性能最优的单例写法
*
* 存在问题:反射调用的时候会破坏此单例模式
*/
public class LazyInnerClassSingleton {
/**
* 私有化构造方法
*/
private LazyInnerClassSingleton() {
throw new RuntimeException("Not support!");//防止反射破坏单例
}
/**
* 懒汉式单例
*
* <p>
* 提供全局访问点
* <p>
* LazyHolder等到此方法调用时,才执行,而内部类又使用饿汉式,完美避免了线程安全问题
*
* @return 单例对象
*/
public static LazyInnerClassSingleton getInstance() {
return LazyHolder.INSTANCE;
}
/**
* 内部类实例单例模式
*/
private static class LazyHolder {
private static final LazyInnerClassSingleton INSTANCE =
new LazyInnerClassSingleton();
}
}
注册式实现
/**
* 容器式单例
*
* 容器式单例都属于注册式单例模式,其核心思想是:
* 在使用时,先去容器中查找,如果找到了,就将查出来的对象返回
* 否则,实例化,然后转载到容器中,最后将实例化的对象返回
*/
public class ContainerSingleton {
/**
* 单例容器
*/
private static final Map<String, Object> ioc = new ConcurrentHashMap<>();
/**
* 私有化构造函数
*/
private ContainerSingleton() {
}
/**
* 容器式单例模式
*
* 存在线程安全问题,因此需要使用synchronized关键字来加锁
*
* @param key 获取单例的key
* @return 单例对象
*/
public static Object getBean(String key) {
if (ioc.containsKey(key)) {//如果有就取出返回
return ioc.get(key);
}
//如果没有,新建-装载-返回
try {
Object instance = Class.forName(key).newInstance();
ioc.put(key, instance);
return instance;
} catch (Exception e) {
e.printStackTrace();
}
//装载异常, 返回空
return null;
}
}
最典型的就是枚举式单例
/**
* 枚举单例模式
* 属于装载类单例模式
* <p>
* 在调用时,先查询容器中是否有此对象的实例,有就取出直接返回,否则新建一个实例并且将其装载到容器中
* 体现在Enum.valueOf((Class)cl, name);这个方法上
*/
public enum EnumSingleton {
INSTANCE;
/**
* 用来扩展的对象
*/
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
/**
* 提供全局访问点
*
* @return 单例对象
*/
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
线程池单例
/**
* 伪线程安全
* 属于注册式单例模式
* 即装载式单例
* <p>
* 案例: ThreadLocal来实现多数据源动态切换
*/
public class ThreadLocalSingleton {
/**
* ThreadLocal实现单例模式
*/
private static final ThreadLocal<ThreadLocalSingleton> local = new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
/**
* 私有化构造方法
*/
private ThreadLocalSingleton() {
}
/**
* 提供全局访问点
*
* @return 单例对象
*/
public static ThreadLocalSingleton getInstance() {
return local.get();
}
}
简单总结
- 私有化构造器
- 保证线程安全
- 延迟加载
- 防止序列化和反序列化破话单例
- 防御反射攻击单例
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。