本文从最基础的饿汉式及懒汉式demo进行引入,通过jdk源码分别分析了:反射及反序列化破坏单例原理、readResolve()如何防止反序列化破坏单例、枚举式单例的优点及如何防止反射及反序列化破坏、以及spring容器式单例思想详解。
/**
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:某些情况下,可能会造成内存浪费
*/
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
/**
* 静态代码块
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅,并且加锁会产生性能问题
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
//指令重排序的问题
}
}
}
return instance;
}
}
/*
优点:写法优雅,利用了Java本身语法特点,性能高,避免了内存浪费,不能被反射破坏
缺点:不优雅
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
if(LazyHolder.INSTANCE != null){
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
public class ReflectTest {
public static void main(String[] args) {
try {
//获取单例类的class及构造器
Class<?> clazz = LazyDoubleCheckSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
//设置强制访问
c.setAccessible(true);
//实例化两次
Object instance1 = c.newInstance();
Object instance2 = c.newInstance();
//分别打印
System.out.println(instance1);
System.out.println(instance2);
//false
System.out.println(instance1 == instance2);
}catch (Exception e){
e.printStackTrace();
}
}
}
//一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象进行反序列化,然后将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton)ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
写在前面:虽然解决了单例模式被破坏的问题,但是实际上实例化了两次,只不过新创建的对象没有被返回,根据如下调用栈即可探明真相readObject()【ObjectInputStream】->readObject0(false)【ObjectInputStream】->readOrdinaryObject(unshared)【ObjectInputStream】
s1 = (SeriableSingleton)ois.readObject();
//去掉无用代码
public final Object readObject() {
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
}
private Object readObject0(boolean unshared) throws IOException {
//去掉无用代码
switch (tc) {
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
}
private Object readOrdinaryObject(boolean unshared) {
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
//1. 实例化反序列化对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
//如果单例对象存在readResolve(),则对第一步【1. 实例化反序列化对象】产生的对象进行覆盖
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
//进行覆盖
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
}
return obj;
}
写在前面:枚举式单例模式,无法通过反射及反序列化来破坏单例,是实现单例模式最为优良的方式,并且《Effective Java》一书也推荐使用枚举来实现单例
public class EnumSingletonTest {
public static void main(String[] args) {
//构建两个实例对象
System.out.println("测试枚举类型单例--start");
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
System.out.println("是否为同一对象:" + (instance1 == instance2));
System.out.println();
try {
System.out.println("测试反射能够破坏单例--start");
//测试通过反射能否破坏单例
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
Object o = c.newInstance();
}catch (Exception e){
System.out.println("发生异常,不允许通过反射构造Enum实例"+e.getMessage());
}finally {
System.out.println();
}
try {
EnumSingleton enumSingleton1 = EnumSingleton.getInstance();
EnumSingleton enumSingleton2 = null;
System.out.println("测试反序列化能够破坏单例--start");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("EnumSingleton.obj"));
oos.writeObject(enumSingleton1);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("EnumSingleton.obj"));
enumSingleton2 = (EnumSingleton)ois.readObject();
System.out.println("是否为同一对象:" + (enumSingleton1 == enumSingleton2));
}catch (Exception e){
System.out.println("发生异常,不允许通过反序列化破坏单例"+e.getMessage());
}
}
}
enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
为什么想要通过反射破环单例时,获取构造方法时要传入两个参数呢?
clazz.getDeclaredConstructor(String.class,int.class);
查看java.lang.Enum类的源码即可发现,其只含有这一个构造器
Enum类型是如何防止反序列化破坏单例的。
Enum.valueOf((Class)cl, name);
private Enum<?> readEnum(boolean unshared) throws IOException {
ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
@SuppressWarnings("unchecked")
//通过类名及类对象找到唯一的枚举类
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
//获取存储的Enum类对象
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
写在前面:Enum实现单例虽有有众多优点,但是当单例数量众多时却不方便管理。仿照spring思想,如果通过一个容器同一存储则更方便管理,但是该方法实现的单例线程不安全也容易被破坏。
public class ContainerSingletonTest {
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("singleton.container.ContainerSingleton");
Object instance2 = ContainerSingleton.getInstance("singleton.container.ContainerSingleton");
System.out.println(instance1 == instance2);
}
}
class ContainerSingleton {
private ContainerSingleton(){}
//通过容器管理所有的实例
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
c.setAccessible(true);
▄█▀█●各位同仁,如果我的代码对你有帮助,请给我一个赞吧,为了下次方便找到,也可关注加收藏呀
如果有什么意见或建议,也可留言区讨论