单例模式
模式定义:
保证一个类只有一个实例,并且提供一个全局访问点
场景:
重量级的对象,不需要多个实例,如线程池,数据库连接池。
类图:
懒汉模式
延迟加载,只有在真正使用的时候,才开始实例化。
1)线程安全问题
2)double check 加锁优化
3)编译器(JIT),CPU有可能对指令进行重排序,导致使用到尚未初始化的实例,可以通过添加volatile关键字进行修饰
对于volatile修饰的字段,可以防止指令重排。
public class LazySingleton{
private volatile static LazySingleton instance;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(instance == null){
synchronized(LazySingleton.class){
if(instance == null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
饿汉模式
类加载的初始化阶段就完成了实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)
类加载过程
1)加载二进制数据到内存中,生成对应的Class数据结构
2)连接:验证->准备(给类的静态成员变量赋默认值)->解析
3)初始化:给类的静态变量赋初值 只有在真正使用对应的类时,才会触发初始化,如当前类是启动类即main函数所在类、直接进行new操作、访问静态属性、访问静态方法、用反射访问类、初始化一个类的子类等
public class HungrySingleton{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
静态内部类
1)本质上是利用类的加载机制来保证线程安全
2)只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一种形式
public class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
反射攻击实例
Constructor<InnerClassSingleton> declaredConstructor=InnerClassSingleton.c lass.getDeclaredConstructor();
declaredConstructor.setAccessible( true );
InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance();
InnerClassSingleton instance=InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton==instance);
静态内部类防止反射破坏
public class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if(InnerClassHolder.instance != null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
}
枚举类型
1)天然不支持反射创建对应的实例,且有自己的反序列化机制
2)利用类加载机制保证线程安全
public enum EnumSingleton {
INSTANCE;
public void print(){
System.out.println(this.hashCode());
}
}
序列化
可以利用指定方法来替换从反序列化流中的数据,代码如下
Serializable类中对于方法readResolve()的说明:
public class InnerClassSingleton implements Serializable {
public static final long serialVersionUID = 42L;
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton(){
if(InnerClassHolder.instance != null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static InnerClassSingleton getInstance(){
return InnerClassHolder.instance;
}
public Object readResolve() throws ObjectStreamException{
return InnerClassHolder.instance;
}
}
源码中的应用
Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
反序列化指定数据源
java.util.Currency