设计模式(2):单例模式

本文详细介绍了单例模式的作用、应用场景以及其优点,列举了饿汉式、懒汉式、双重检查锁、静态内部类和枚举单例等五种常见实现方式,并讨论了如何通过反射和反序列化破解单例及相应的防护措施。
摘要由CSDN通过智能技术生成

核心作用:

  • 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

常见应用场景:

  • windows的任务管理者(Task Manager)就是很典型的单例模式;
  • 在spring中,每个Bean默认就是单例的,这样做的优点是spring容器可以管理;
  • 在servlet编程中, 每个servlet也是单例;
  • 项目中,读取配置文件的类,一般也只有一个对象,没有必要每次使用配置文件数据,每次new一个对象去读取。

单例模式优点:

  • 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其它依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式类解决。
  • 单例模式可以在系统设置全局的访问点,优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

常见的五种单例模式实现方式:

  • 主要:
    • 饿汉式(线程安全,调用效率高。但是,不能延时加载)
    • 懒汉式(线程安全,调用效率低。但是,可以延时加载)
  • 其它:
    • 双重检查锁(线程安全,可以延时加载。建议使用)
    • 静态内部类(线程安全,调用效率高。并且,可以延时加载)
    • 枚举单例(天生线程安全,调用效率高。但是,不能延时加载)

饿汉式:

/**
 *  饿汉式   --->线程安全,方法没有同步,效率高!但是,没有延时加载优势
 *  1,构造器私有化,避免外部直接创建对象
 *  2,声明一个私有的静态属性  并  创建该对象
 *  3,创建一个对外的公共的静态方法,访问该变量,
 * @author admin
 *
 */
public class SingLeton {
	/**
	 * 2,声明一个私有的静态属性  并  创建该对象
	 * 			类初始化时,立即加载这个对象(没有延时加载优势),加载类时,天然的是线程安全的!(所有。。)
	 */	
	private static SingLeton instance =new SingLeton();
	/**
	 * 1,构造器私有化,避免外部直接创建对象
	 */
	private SingLeton() {
	}
	/**3,创建一个对外的公共的静态方法,访问该变量。
	 * 			方法没有同步,效率高!
	 * @return
	 */
	public static SingLeton getInstance(){    //
		return instance;	
	}
}

懒汉式:

/**
 *	  懒汉式--->线程安全,调用效率不高!但是,可以延时加载
 *	  1,构造器私有化,避免外部直接创建对象
 *	  2,声明一个私有的静态属性
 *    3,创建一个对外的公共的静态方法,访问该变量,如果变量没有对象,创建该对象, 
 * @author admin
 */
public class SingLeton {
	/**
	 * 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
	 */
	private static SingLeton instance;
	/**
	 * 私有构造器只供自己使用
	 */
	private SingLeton() {
	}
	/**
	 * 方法同步,效率低
	 * @return
	 */
	public static synchronized SingLeton getInstance(){
		if(null==instance){
			instance =new SingLeton();
		}
		return instance;
	}
}

静态内部类:

/**
 * ͨ 通过静态内部类实现単例模式
 * 这种方法:线程安全,调用效率高,并且实现了延时加载
 * @author admin
 */
public class SingLeton {	
	/**
	 * 只有调用getInstance()方法时,这个类才会被加载
	 * @author admin
	 */
	private static class SingLetonInstance{
		private static final SingLeton instance=new SingLeton();
	} 	
	private SingLeton() {
	}	
	/**
	 * 方法没有同步,效率高!
	 * @return
	 */
	public static SingLeton getInstance(){
		return SingLetonInstance.instance;	
	}	
}

枚举单例

/**
* 		枚举式实现単例模式!(没有延时加载)
* 		可以天然防止反射和反序列化漏洞
* @author admin
*/
public enum SingLeton{
  /**
   * 这个枚举元素,本身就是単例对象
   */
  Instance;	
  /**
   * 可以添加自己需要的操作
   */
  public void SingLeton(){
  }
}

双重检查锁

/**
 * 通过双重检查锁实现単例模式
 *  线程安全,可以延时加载。建议使用
 * @author admin
 */
public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {
    }
    public Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

接下来给大家讲一下如何通过反射和反序列化破解单例?如何防止反射和反序列化破解单例??

反射如何破解单例?

懒汉式单例类
/**
 * 单利设计模式     
 *	  懒汉式
 * @author admin
 */
public class SingLeton implements Serializable{
	/**
	 * 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
	 */
	private static SingLeton instance;	
	private SingLeton()  {
	}
	/**
	 * 方法同步,效率低
	 * @return
	 */
	public static synchronized SingLeton getInstance(){
		if(null==instance){
			instance =new SingLeton();
		}
		return instance;
	}
}
通过反射破解单例
public static void main(String[] args) throws Exception {
		/**
		 * 反射如何破解単例(通过直接调用私有空构造器)
		 */
		Class<SingLeton> clazz=(Class<SingLeton>) Class.forName("com.reyco.design.singLeton.SingLeton");
		Constructor<SingLeton> c=clazz.getDeclaredConstructor(null);
		//忽略检查
		c.setAccessible(true);
		SingLeton s1=c.newInstance();
		SingLeton s2=c.newInstance();
		//比较是不是同一个对象
		System.out.println(s1==s2);
}

如何防止反射破解单例?

  • 可以在构造器方法中手动抛出异常
/**
 * 单利设计模式     
 *	  懒汉式--->(如何防止反射和反序列化漏洞)
 * @author admin
 */
public class SingLeton implements Serializable{
	/**
	 * 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
	 */
	private static SingLeton instance;	
	private SingLeton()  {
		//防止反射漏洞:如果有对象了,再次调用,就抛出RuntimeException异常.
		if(null!=instance){
			throw new RuntimeException();
		}	
	}
	/**
	 * 方法同步,效率低
	 * @return
	 */
	public static synchronized SingLeton getInstance(){
		if(null==instance){
			instance =new SingLeton();
		}
		return instance;
	}
}

如何通过反序列化破解单例?

/**
 * 反序列化如何破解単例(除了枚举)
 * @author admin
 */
public static void main(String[] args) throws Exception {
	/**
	 * 反序列化如何破解単例
	 */
	SingLeton s1 = SingLeton.getInstance();
	//序列化
	ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("E:/test/09.txt"));
	oos.writeObject(s1);
	oos.flush();
	oos.close();
	//反序列化
	ObjectInputStream ois=new ObjectInputStream(new FileInputStream("E:/test/09.txt"));
	SingLeton s2=(SingLeton) ois.readObject();
	System.out.println(s1==s2);
}

如何防止反序列化破解单例?

  • 可以通过定义readResolve()方法防止获取不同对象。反序列化时,如果对象所在类定义了readResolve()方法,则直接返回此方法指定的对象,而不需要单独创建对象.
/**
 * 单利设计模式     
 *	  懒汉式--->(如何防止反射和反序列化漏洞)
 * @author admin
 */
public class SingLeton implements Serializable{
	/**
	 * 类初始化时,不初始化这个对象(延时加载,真正用的时候在创建)
	 */
	private static SingLeton instance;	
	private SingLeton()  {	
	}
	/**
	 * 方法同步,效率低
	 * @return
	 */
	public static synchronized SingLeton getInstance(){
		if(null==instance){
			instance =new SingLeton();
		}
		return instance;
	}
	
	/**
	 * 防止反序列化漏洞(反序列化,如果定义了readResolve方法,则直接返回此方法指定的对象,而不需要单独创建对象)
	 * @return
	 * @throws Exception
	 */
	public Object readResolve()throws Exception{
		return instance;
	}
}



更多设计模式学习:

          设计模式(1):介绍
          设计模式(2):单例模式
          设计模式(3):工厂模式
          设计模式(4):建造者模式
          设计模式(5):原型模式
          设计模式(6):桥接模式
          设计模式(7):装饰器模式
          设计模式(8):组合模式
          设计模式(9):外观模式
          设计模式(10):享元模式
          设计模式(11):适配器模式
          设计模式(12):代理模式
          设计模式(13):模板方法模式
          设计模式(14):命令模式
          设计模式(15):迭代器模式
          设计模式(16):观察者模式
          设计模式(17):中介者模式
          设计模式(18):状态模式
          设计模式(19):策略模式
          设计模式(20):责任链模式
          设计模式(21):备忘录模式
          设计模式(22):解释器模式
          设计模式(23):访问者模式

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java的艺术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值