目录
1 单例模式概念
2 单例模式的演示
3 使用反射和序列化破解懒汉单例模式 以及如何防漏洞
概念
单例模式,就是一个类只有一个实例对象,不管怎么做,都只有这个一个实例对象
单例模式优点:只生成一个实例,减少了性能开销,当一个对象的生产需要比较多的资源时,如读取配置 产生其他依赖对象时,则可以通过应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化环共享资源访问时,例如可以设计一个单类负责所有数据表的映射处理
常见的五种单例模式的实现方式 主要:1 饿汉模式(线程安全,调用效率高,但是不能延迟加载) 2懒汉模式(线程安全,调用效率不高,可以延时加载)
其他 1 双重检测锁模式(由于jvm底层模型原因,会出问题,不推荐使用)2 静态内部类式(线程安全,调用效率高,但是,可以延迟加载,顾全了 饿汉模式和懒汉模式的有点,但又没有其缺点)3 枚举单例(线程安全,调用效率高,不能延时加载)
代码示例 一 懒汉模式 (线程安全,调用效率不高,能延迟加载)
package singleton; public class SingletonDemo1 { // 将构造方法私有化,防止外部访问,因此不可new出此类的对象 private SingletonDemo1() { // TODO Auto-generated constructor stub } // 定义静态的本类对象 private static SingletonDemo1 sing; // 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的 public static synchronized SingletonDemo1 getInstance() { // 如果sing==null表明还没有被new new出一个sing返回 否则直接返回sing if (sing == null) { sing = new SingletonDemo1(); return sing; } else { return sing; } } }
package singleton; public class SingletonTest { public static void main(String[] args) { SingletonDemo1 d1 = SingletonDemo1.getInstance(); SingletonDemo1 d2 = SingletonDemo1.getInstance(); SingletonDemo1 d3 = SingletonDemo1.getInstance(); System.out.println(d1); System.out.println(d2); System.out.println(d3); } }
对象内存地址一致
代码示例 二 饿汉模式 (线程安全,调用效率高,但是不能延迟加载)
package singleton; public class SingletonDemo02 { // 将构造方法私有化,防止外部访问,因此不可new出此类的对象 private SingletonDemo02() { // TODO Auto-generated constructor stub } // 定义静态的本类对象 直接new出实例 private static final SingletonDemo02 sing = new SingletonDemo02(); // 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的 public static synchronized SingletonDemo02 getInstance() { return sing; } }
package singleton; public class SingletonTest { public static void main(String[] args) { SingletonDemo02 d1 = SingletonDemo02.getInstance(); SingletonDemo02 d2 = SingletonDemo02.getInstance(); System.out.println(d1); System.out.println(d2); } }
内存地址一致
示例三 内部静态类方式 (线程安全,调用效率高,但是,可以延迟加载,顾全了 饿汉模式和懒汉模式的有点,但又没有其缺点)
package singleton; public class SingletonDemo03 { //定义一个静态内部类 private static class SingStatic { private static final SingletonDemo03 sing = new SingletonDemo03(); } //公开的方法,不需要加synchronized 因为他本身就是线程安全的 public static SingletonDemo03 getInstance() { return SingStatic.sing; } }
package singleton; public class SingletonTest { public static void main(String[] args) { SingletonDemo03 d1 = SingletonDemo03.getInstance(); SingletonDemo03 d2 = SingletonDemo03.getInstance(); System.out.println(d1); System.out.println(d2); } }
示例四 枚举式单例模式 (线程安全,调用效率高,不难延时加载)
package singleton; /** * 枚举方式可以避免 反射和反序列化的漏洞,调用效率也比较高,很遗憾没有延迟加载 * * @author re * */ public enum SingletonDemo04 { // 这个枚举元素本身就是单例对象 instance; // 可以写自己想要的操作 public void printenum() { System.out.println("这是枚举示例"); } }
package singleton; public class SingletonTest { public static void main(String[] args) { SingletonDemo04 d1 = SingletonDemo04.instance; SingletonDemo04 d2 = SingletonDemo04.instance; System.out.println(d1 == d2); } }
比较为true 是同一个对象
破解单例模式 和防漏洞 (枚举无法破解,因为它是基于JVM底层实现的)
一懒汉模式为例,使用反序列化和反射进行破解
1 使用反射破解
package singleton; public class SingletonDemo1 { // 将构造方法私有化,防止外部访问,因此不可new出此类的对象 private SingletonDemo1() { // TODO Auto-generated constructor stub } // 定义静态的本类对象 private static SingletonDemo1 sing; // 写一个公开方法给外部调用 通过该方法获取实例对象 加上synchronized设为线程安全的 public static synchronized SingletonDemo1 getInstance() { // 如果sing==null表明还没有被new new出一个sing返回 否则直接返回sing if (sing == null) { sing = new SingletonDemo1(); return sing; } else { return sing; } } }
package singleton; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class SingletonTest { public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // 获取class对象 Class clazz = SingletonDemo1.class; // 获取构造器 Constructor c1 = clazz.getDeclaredConstructor(null); c1.setAccessible(true);// 跳过访问检查 如果不设为true 则无法访问私有属性, // 因此可以访问私有的构造方法,从而创建对象,利用这个漏洞可以破解单例模式 SingletonDemo1 d1 = (SingletonDemo1) c1.newInstance(null); SingletonDemo1 d2 = (SingletonDemo1) c1.newInstance(null); System.out.println(d1); System.out.println(d2); } }
输出的 对象地址不一样 将SinglentoDemo1中的私有构造方法改为 可防止漏洞,不过我的这个有些问题,加上之后当sing!=null new对象时,并不能抛出异常。
private SingletonDemo1() { // TODO Auto-generated constructor stub if(sing!=null){ //如果有已有sing实例,表示被new过,再创建对象时抛出异常 throw new RuntimeException(); } }
反序列化防止漏洞方法
在单例类中加入 下面这个方法 方法名称和返回值类型不要改动
// 在反序列化时,直接调用这个方法,返回指定的对象,无需再新建一个对象 private Object readResolve() { return sing; }
枚举式单例模式和静态内部类单例模式如何选择