目录
三、解决反射、序列化破解上面几种实现方式的漏洞(除枚举式外)
一、基本介绍
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:1. 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他 依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
2.单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理.
常见的五种单例模式实现方式:
主要有:饿汉式(天然的线程安全,调用效率高。 但是,不能延时加载。)
懒汉式(需加锁保证线程安全,调用效率不高。 但是,可以延时加载。)
其 他:双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。 可以延时加载)
枚举单例(线程安全,调用效率高,不能延时加载)
二、代码实现
①:饿汉式(天然的线程安全,调用效率高,不可以延迟加载)
优点:天然的线程安全不用加锁调用效率高。
缺点:在类加载初始化时就创建好一个静态的对象供外部使用(立即加载),可能未被使用导致资源利用率低
public class SingletonDemo01 {
private static SingletonDemo01 instance = new SingletonDemo01();
//私有构造方法,禁止被new对象
private SingletonDemo01(){
}
public static SingletonDemo01 getInstance(){
return instance;
}
}
②:懒汉式(需加锁保证线程安全,调用效率低,可以延迟加载)
有点:可以延迟加载,只有当被使用才会构建实例,资源利用率高
缺点:在方法上加synchronized同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
public class SingletonDemo02 {
//不初始化,延迟加载
private static SingletonDemo02 instance = null;
//私有构造方法,禁止被new对象
private SingletonDemo02(){
}
public static synchronized SingletonDemo02 getInstance(){
if(instance==null){
instance = new SingletonDemo02();
}
return instance;
}
}
③:双重检测锁
改良了懒汉式加载直接锁整个方法,只对需要锁的代码部分加锁,调用效率比懒汉式高
问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题,不建议使用
问题详细描述:在new一个对象时 ,会分配一块内存、在内存里赋以默认值、init初始化、将初始化完后的对象引用返回,在这个过程中如果编译器进行了重排序就会导致其实已经new了一个SingletonDemo03,只不过此时默认值为null,冉后就将其的引用返回,之后在进行初始化,这样就有可能后续其他线程调用getInstance()方法刚好这个时候进来,就无法保证单例性。
public class SingletonDemo03 {
//不初始化,延迟加载
private static SingletonDemo03 instance = null;
//私有构造方法,禁止被new对象
private SingletonDemo03(){
}
public static SingletonDemo03 getInstance(){
if(instance==null){
synchronized(SingletonDemo03.class){
if (instance==null)
instance = new SingletonDemo03();
}
}
return instance;
}
}
解决方案:加volatile关键字修饰变量,因为volatile可以禁止重排序。
④:静态内部类的实现式
优点:提高资源利用率(静态内部类只有被调用的时候才会被加载,所以该模式可以延迟加载)
线程安全(instance是static final类型,保证了内存中只有这样一个实例存在,只能被赋值一次,保证了线程安全性)
public class SingletonDemo04 {
//私有构造方法,禁止被new对象
private SingletonDemo04(){
}
public static SingletonDemo04 getInstance(){
return SingletonInstance.instance;
}
public static class SingletonInstance{
private static final SingletonDemo04 instance = new SingletonDemo04();
}
}
⑤:枚举式
优点:实现简单、枚举本身就是单例模式,由JVM提供根本保障,避免了通过反射和反序列化的漏洞。
缺点:无法延迟加载
当其他类使用EnumDemo demo = EnumDemo.INSTANCE;构建一个实例时,这个EnumDemo枚举类的构造函数永远只会被加载一次,所以直接就是单例模式!
public enum EnumDemo {
//这个枚举元素,本身就是单例对象
INSTANCE;
//添加其他需要的操作
public void OtherOperation() {
}
}
三、解决反射、序列化破解上面几种实现方式的漏洞(除枚举式外)
①反射漏洞
如下代码是使用反射出SingletonDemo01即饿汉式单例模式,尽管饿汉式单例模式私有化了构造方法,但还是通过反射方式创建出新的实例对象。
其中c.setAccessible(true);是将c对应的Java类私有构造器设置成可进入的
public class test {
public static void main(String[] args) throws Exception{
Class<SingletonDemo01> clazz = (Class<SingletonDemo01>)Class.forName
("com.design.priciple.Singleton.SingletonDemo01");
Constructor<SingletonDemo01> c = clazz.getDeclaredConstructor();
//将类的构造方法设置成可进入的
c.setAccessible(true);
SingletonDemo01 s1 = c.newInstance();
System.out.println("s1="+s1);
}
}
如何结果呢? 其实很简单只需要在构造方法中抛出异常即可!
下面的代码是将饿汉式单例模式中的构造方法添加了一句if判断,当有其他类只能进入了SingletonDemo01类的构造方法就会抛出异常!
public class SingletonDemo01 {
private static SingletonDemo01 instance = new SingletonDemo01();
//私有构造方法,禁止被new对象
private SingletonDemo01(){
if(instance != null){
throw new RuntimeException();
}
}
public static SingletonDemo01 getInstance(){
return instance;
}
}
②反序列化漏洞
当通过将饿汉式单例模式的实例对象序列化、反序列化后得到的会是另一个实例对象,与序列胡的对象不同
public class test{
public static void main(String[] args) throws Exception{
SingletonDemo01 s1 = SingletonDemo01.getInstance();
//序列化
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo01 s2 = (SingletonDemo01)ois.readObject();
System.out.println(s1==s2);
}
}
解决方案:只需在单例模式代码中加入一个readResolve()方法
该方法会在反序列化时,会直接调用这个方法返回instance对象,不会再返回新对象
public class SingletonDemo01 implements Serializable {
private static SingletonDemo01 instance = new SingletonDemo01();
//私有构造方法,禁止被new对象
private SingletonDemo01(){
}
public static SingletonDemo01 getInstance(){
return instance;
}
private Object readResolve(){
return instance;
}
}
加了radeResolve()方法后,再次运行代码输出"true"