目 录
前言
单例模式要求在任何情况下目标对象都只存在一个实例,并提供一个全局访问点获取该对象的实例。常见的应用场景如:1. 网站浏览次数计数器,采用单例模式可以避免线程安全问题;如果存在多个计数器对象,那么计数器的值是无法同步的。2. 系统配置信息,配置信息通常由一个单例对象统一管理,在服务程序中其他对象从该单例对象中获取所需的配置信息。单例模式大致可分为四类:饿汉式、懒汉式、注册式和 ThreadLocal 式。
单例模式写法
1. 饿汉式
以下这两种形式异曲同工,无太大差别。
1.1 直接初始化
优点:执行效率高,性能高,未加任何锁。
缺点:某些情况下(当应用程序中单例对象比较多时),可能造成内存浪费。
public class SystemConfigHungry {
//初始化
private static final SystemConfigHungry instance = new SystemConfigHungry();
//构造方法私有化
private SystemConfigHungry(){}
//访问点
public static SystemConfigHungry getInstance(){
return instance;
}
}
1.2 静态代码块初始化
优点:执行效率高,性能高,未加任何锁。
缺点:某些情况下(当应用程序中单例对象比较多时),可能造成内存浪费。
public class SystemConfigHungry {
private static SystemConfigHungry instance = null;
//初始化
static {
instance = new SystemConfigHungry();
}
//构造方法私有化
private SystemConfigHungry(){}
//访问点
public static SystemConfigHungry getInstance(){
return instance;
}
}
2. 懒汉式
2.1 普通方式
优点:节省内存,线程安全。
缺点:性能低。
public class SystemConfigLazy {
private static SystemConfigLazy instance = null;
//构造方法私有化
private SystemConfigLazy(){}
//访问点、初始化
public synchronized static SystemConfigLazy getInstance(){
if(instance == null){
instance = new SystemConfigLazy();
}
return instance;
}
}
2.2 双重检查锁
优点:性能高,线程安全。
缺点:代码可读性差,不优雅。
public class SystemConfigDoubleCheck {
private volatile static SystemConfigDoubleCheck instance = null;
//构造函数私有化
private SystemConfigDoubleCheck(){}
//初始化、访问点
public static SystemConfigDoubleCheck getInstance(){
if(instance == null){
synchronized (SystemConfigDoubleCheck.class){
if(instance == null){
//指令重排序问题:分配变量 instance 变量和 new SystemConfigDoubleCheck() 的内存先后问题。
instance = new SystemConfigDoubleCheck();
}
}
}
return instance;
}
}
2.3 静态内部类
优点:代码结构优雅,利用 java 本身的语法特点,性能高,避免内存浪费。
缺点:能够被反射破坏。
public class SystemConfigInnerClass {
//构造函数私有化
private SystemConfigInnerClass(){}
//访问点、延迟加载
public static SystemConfigInnerClass getInstance(){
return SystemConfigInit.instance;
}
//内部类初始化
private static class SystemConfigInit{
private static final SystemConfigInnerClass instance = new SystemConfigInnerClass();
}
}
3. 注册式
3.1 枚举式
优点:线程安全,代码优雅。
缺点:不能大批量创建对象,会造成内存浪费。
public enum SystemConfigEnum {
INSTANCE;
//1. Enum 本身只提供了有参构造器,但是在类 Constructor 已规定不能通过反射实例化枚举类;
//2. Enum 本身实现了 Serializable接口,禁止了 readObject 方法。只能通过自身提供的 valueOf方法获取
// name对应的 Enum 对象,方法体本身是使用 Map 作为容器存储已初始化的枚举对象(声明时已存储)
public static SystemConfigEnum getInstance(){
return INSTANCE;
}
}
3.2 容器式
容器式(IOC容器),将每个实例缓存到容器中,使用唯一标识获取实例。
public class ContainerSingleton {
private static Logger logger = LoggerFactory.getLogger(ContainerSingleton.class);
//初始化容器
private static Map<String, Object> container = new ConcurrentHashMap<String, Object>(16);
/**
* 访问点
* @param className 类全路径(eg.com.design.service.impl.share.BaseDTO)
*/
public static Object getInstance(String className){ //className:类全路径
Object object = null;
if(!container.containsKey(className)){
try {
object = Class.forName(className).newInstance();
container.put(className, object);
} catch (Exception e) {
logger.error("Init instance exception", e);
}
}else{
object = container.get(className);
}
return object;
}
}
4. ThreadLocal 式
严格意义上,此方式并非单例。只是在单个线程中是单例。比如:ORM框架中实现多数据源动态切换。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadInstance = new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
//构造方法私有化
private ThreadLocalSingleton(){}
//访问点
public static ThreadLocalSingleton getInstance(){
return threadInstance.get();
}
}
总结
- 单例模式常应用于创建对象耗时较长或耗费资源较多、频繁访问数据库或资源文件的对象;
- 破坏单例模式的方式有多线程、反射、序列化-反序列化等,使用时需多加留意。