多classloader对单例模式的影响

本文通过一个具体的示例探讨了单例模式在不同类加载器环境下的行为表现,特别是当两个相同名称的类由不同类加载器加载时,单例模式的表现会如何变化。文章还讨论了这种现象在实际应用中的意义,例如在Tomcat环境中部署多个项目时的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

早上看了一篇写单例模式的文章,总结的比较全,可惜仍然没有对单例模式在不同classloader甚至是多个jvm上的分析,在网上搜了一下,找到一个例子并改写如下:

定义一个空接口:
// Null interface, do nothing but you'll see the usage later
public interface IAntiSingleton {
}


定义一个通用的单例模式
public class AntiSingleton implements IAntiSingleton {
private static final AntiSingleton instance = new AntiSingleton();

public static AntiSingleton getInstance() {
return instance;
}
}


继承一个classloader并生成AntiSingleton对象
public class NewClassLoader extends ClassLoader {
public IAntiSingleton createNewOne() throws Exception {
InputStream is = getClass().getResourceAsStream("AntiSingleton.class");
byte[] b = new byte[is.available()];
is.read(b);
Class clz = defineClass(null, b, 0, b.length);
Object o = clz.newInstance();
return (IAntiSingleton) o;
}
}


测试方法:
public class TestAntiSingleton {
public static void main(String[] args) throws Exception {
AntiSingleton instance = AntiSingleton.getInstance();
NewClassLoader loader = new NewClassLoader();
IAntiSingleton newObj = loader.createNewOne();
System.out.println(AntiSingleton.getInstance() == newObj);
}
}
output:
=========
false


[b]总结:[/b]

java中,一个类可以有多个定义,并且这些类名可以相同(但同一个类加载器产生类的类名不能相同)。如果不同类加载器两个类名相同,即使类的定义相同,甚至一个类的定义是由另一个类产生的,这两个类也是不同的类。

这也就是输出结果产生false的原因,因为此时两个类虽然名字相同,但是其实已经不是一个类。此时的单例模式,看起来是失效的。注意上面为什么要用接口,因为此时在程序中直接使用NewClassLoader用AntiSingleton对输出结果进行转型,在运行时就会抛出一个类型转换Exception。

可以想到的一种情况是,tomcat中,可以部署很多项目,各个项目中是可以出现相同的类名的,这就需要不同的classloader分别load生成这些类。

此外,GoF计划对“设计模式”进行修订,其中提到了要剔除“单例模式”,很期待修订版早点出来。
<< 单例模式(Singleton Pattern)确保某一个类只有一个实,并且自行提供这个全局访问点。根据其实现方式的不同单例模式可以分为以下几种: ### 1. 饿汉式 在饿汉式中,类加载时就完成了实化操作,因此线程安全并且简高效,但可能会导致资源浪费。 ```java public class Singleton { private static final Singleton INSTANCE = new Singleton(); // 私有构造器防止被外部实化 private Singleton() {} public static Singleton getInstance() { return INSTANCE; } } ``` **优点**: 类在装载时就已经完成初始化, 不用担心线程下的安全性问题; **缺点**: 占用了内存空间(即使没有使用); --- ### 2. 懒汉式 懒汉式直到第一次调用 `getInstance` 方法才创建实,节省了内存开销;但由于可能存在个线程同时调用该方法的情况,需要进行同步处理。 #### (1) **非线程安全版** ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } } ``` **注意**: 这种写法不是线程安全的,在高并发情况下可能出现个实的问题。 #### (2) **线程安全版 - synchronized关键字** ```java public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance(){ if(instance==null) instance=new Singleton(); return instance; } } ``` **虽然解决了线程安全问题**,但是synchronized锁降低了性能. --- ### 3. 双重检查锁定(DCL) 双重校验加锁机制既保证了线程的安全性又提高了效率。 ```java public class Singleton{ private volatile static Singleton singleton;//volatile避免指令重排序 private Singleton(){} public static Singleton getInstatnce(){ if(singleton==null){ //第一层检测 synchronized(Singleton.class){ //上锁只针对instance为null的情况下发生. if(singleton==null){ //第二层检测 singleton= new Singleton();//new 实分配对象到堆内存可能产生三步过程:分配、构建及赋值等步骤, //volatile可避免此情况中的异常现象即未完全构建好就被引用. } } } return singleton; } } ``` **优势**: 解决了延迟加载和线程安全两大难题; **劣势**: 相对复杂一点的理解成本较高。 --- ### 4. 静态内部类 当静态内部类StaticHolder被加载时才会初始化singleton,此时才能确定是否要建立SingleInstance类的对象,从而实现了lazy loading的效果,而且由JVM来帮我们做了线程同步的工作,所以它是线程安全的。 ```java public class Singleton { private Singleton(){} private static class StaticHolder{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return StaticHolder.INSTANCE; } } ``` **特点**: 利用classloader机制保证了线程安全性和唯一性;不会增加过额外负担的同时也达到了延时加载的目的。 --- ### 5. 枚举类型实现 这种方式可能是最简洁明了的一种形式,也是Joshua Bloch推荐的方式之一。它的主要特点是天然地支持序列化与反序列化,并能有效地抵御反射攻击。 ```java public enum SingletonEnum { INSTANCE; } // 使用枚举的方式获取实 SingletonEnum singleton = SingletonEnum.INSTANCE; ``` **优点**: 最安全可靠的做法;自动具备序列化的功能;无法通过反射破坏规则; **总结**: 如果不考虑兼容旧版本的语言特性的需求的话,那么枚举类型的单例模式就是最好的选择方案!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值