定义:
想确保任何情况下都绝对只有一个实例
优点:
1、在内存里只有一个实例,减少了内存开销
2、可以避免对资源的多重占用
3、设置全局访问点,严格控制访问
缺点:没有接口,扩展困难
重点:
1、私有构造器
2、线程安全
3、延迟加载
4、序列化和反序列化安全
5、防止反射攻击
实现方式:
1、Double Check(双重检查锁)
2、静态内部类
Coding
Double Check模式:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
//1、分配内存给这个对象
//2、初始化对象
//3、设置lazyDoubleCheckSingleton 指向刚分配的内存地址
//如果存在指令重排序的话 即 1、3、2的话 会存在问题。
}
}
}
return lazyDoubleCheckSingleton;
}
}
volatile的作用就是防止指令重排序(如下图是重排序后的问题):
静态内部类:
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
}
饿汉式
import java.io.Serializable;
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
单例模式的破坏:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance == newInstance);
}
}
false
进行修改:
import java.io.Serializable;
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
//新增方法
public Object readResolve() {
return hungrySingleton;
}
}
再次执行:
true
解析:
Test.java HungrySingleton newInstance = (HungrySingleton) ois.readObject();
java.io.ObjectInputStream#readObject Object obj = readObject0(false);
java.io.ObjectInputStream#readObject0 case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));
java.io.ObjectInputStream#readOrdinaryObject obj = desc.isInstantiable() ? desc.newInstance() : null;//创建了新的实例
java.io.ObjectInputStream#readOrdinaryObject desc.hasReadResolveMethod()) //判断是否存在readResolve方法,(注释解读)
java.io.ObjectInputStream#readOrdinaryObject Object rep = desc.invokeReadResolve(obj); //调用readResolve方法
防止单例模式反射攻击:
//反射攻击
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<HungrySingleton> declaredConstructor = HungrySingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = declaredConstructor.newInstance();
System.out.println(instance == newInstance);
}
false
解决:
import java.io.Serializable;
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
//防止反射调用
if (hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
//防止反序列化
public Object readResolve() {
return hungrySingleton;
}
}
注:双重检查锁 模式防止单例破坏 是无法做到的,我们可以通过定义flag==true,然后在构造方法中 判断flag是否为true,为true则设置为false,否则抛出异常, 但是反射还是可以动态修改flag的值,所以还是无法阻止单例破坏。
枚举解决反射破坏单例:
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
枚举序列化校验:
public static void main(String[] args) throws IOException, ClassNotFoundException {
EnumInstance instance = EnumInstance.getInstance();
instance.setData(new Object());
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
EnumInstance newInstance = (EnumInstance) ois.readObject();
System.out.println(instance == newInstance);
System.out.println(instance.getData() == newInstance.getData());
}
true
true
枚举反射校验
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumInstance> declaredConstructor = EnumInstance.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumInstance instance = EnumInstance.getInstance();
EnumInstance newInstance = declaredConstructor.newInstance();
System.out.println(instance == newInstance);
System.out.println(instance.getData() == newInstance.getData());
}
//没有找到构造方法
Exception in thread "main" java.lang.NoSuchMethodException: com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.EnumInstance.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.Test.main(Test.java:47)
我们在java.lang.Enum中找到了构造方法:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
我们重试修改测试方法:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumInstance> declaredConstructor = EnumInstance.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumInstance instance = EnumInstance.getInstance();
EnumInstance newInstance = declaredConstructor.newInstance("test", 666);
System.out.println(instance == newInstance);
System.out.println(instance.getData() == newInstance.getData());
}
//提示 枚举类型不能够被反射调用
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.jiangzheng.course.dubbo.provider.pattern.creational.singleton.Test.main(Test.java:50)
彩蛋:
jad的使用:
登录网站下载工具:https://varaneckas.com/jad/
枚举中声明方法
public enum EnumInstance {
INSTANCE{
protected void printTest(){
System.out.println("print text");
}
};
protected abstract void printTest();
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
public static void main(String[] args) {
EnumInstance.getInstance().printTest();
}
基于容器的单例模式:
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private static Map<String,Object> singletonMap = new ConcurrentHashMap<>();
public static void putInstance(String key, Object instance) {
if (StringUtils.isNoneEmpty(key) && instance != null) {
if (singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
基于ThreadLocal的单例模式
//每个线程内部获取时 都是单例的
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocal = new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance() {
}
public static ThreadLocalInstance getInstance() {
return threadLocal.get();
}
}
JDK中Runtime中有使用饿汉式单例模式。
Mybatis中的ErrorContext.java 采用的是ThreadLocal的模式单例