package com.bjsxt.singleton; /** * 测试饿汉式单例模式 * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo1 { //类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的! private static SingletonDemo1 instance = new SingletonDemo1(); private SingletonDemo1(){ } //方法没有同步,调用效率高! public static SingletonDemo1 getInstance(){ return instance; } }
package com.bjsxt.singleton; /** * 测试懒汉式单例模式 * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo2 { //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。 private static SingletonDemo2 instance; private SingletonDemo2(){ //私有化构造器 } //方法同步,调用效率低! public static synchronized SingletonDemo2 getInstance(){ if(instance==null){ instance = new SingletonDemo2(); } return instance; } }
package com.bjsxt.singleton; /** * 双重检查锁实现单例模式 * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo3 { private static SingletonDemo3 instance = null; public static SingletonDemo3 getInstance() { if (instance == null) { SingletonDemo3 sc; synchronized (SingletonDemo3.class) { sc = instance; if (sc == null) { synchronized (SingletonDemo3.class) { if(sc == null) { sc = new SingletonDemo3(); } } instance = sc; } } } return instance; } private SingletonDemo3() { } }
package com.bjsxt.singleton; /** * 测试静态内部类实现单例模式 * 这种方式:线程安全,调用效率高,并且实现了延时加载! * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo4 { private static class SingletonClassInstance { private static final SingletonDemo4 instance = new SingletonDemo4(); } private SingletonDemo4(){ } //方法没有同步,调用效率高! public static SingletonDemo4 getInstance(){ return SingletonClassInstance.instance; } }
实际模式的使用:
package com.bjsxt.singleton; public class Client { public static void main(String[] args) { SingletonDemo4 s1 = SingletonDemo4.getInstance(); SingletonDemo4 s2 = SingletonDemo4.getInstance(); System.out.println(s1); System.out.println(s2); System.out.println(SingletonDemo5.INSTANCE==SingletonDemo5.INSTANCE); } }
反射和反序列化漏洞、多线程环境、CountDownLatch同步类的使用
5种单例模式中除了枚举式,其他都存在反射和反序列化的漏洞,下面来讲述一下:
下面是破解代码:
/** * * 描述:测试反射和反序列化破解单例模式Demo06 * @author cookie */ public class Client { public static void main(String[] args) throws Exception { SingletonDemo06 s1 = SingletonDemo06.getInstance(); SingletonDemo06 s2 = SingletonDemo06.getInstance(); System.out.println(s1); System.out.println(s2); //使用反射方式直接调用私有构造器 Class<SingletonDemo06> clazz = (Class<SingletonDemo06>) Class.forName("com.bjsxt.singleton.SingletonDemo06"); Constructor<SingletonDemo06> c = clazz.getDeclaredConstructor(null); c.setAccessible(true);//绕过权限管理,即在true的情况下,可以通过构造函数新建对象 SingletonDemo06 s3 = c.newInstance(); SingletonDemo06 s4 = c.newInstance(); System.out.println(s3); System.out.println(s4); } }
通过反射引起的单例的漏洞,s3和s4打印出对象的地址是不一样的,如何解决该问题了
package com.bjsxt.singleton; import java.io.ObjectStreamException; import java.io.Serializable; /** * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo6 { //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。 private static SingletonDemo6 instance; private SingletonDemo6(){ //私有化构造器 if(instance!=null){ throw new RuntimeException(); } } //方法同步,调用效率低! public static synchronized SingletonDemo6 getInstance(){ if(instance==null){ instance = new SingletonDemo6(); } return instance; } }
在私有化的构造的函数中判断,抛出一个异常
出了反射能够能够破坏单例之外,序列化也能够破坏单例
/** * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo6 implements Serializable { //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。 private static SingletonDemo6 instance; private SingletonDemo6(){ //私有化构造器 if(instance!=null){ throw new RuntimeException(); } } //方法同步,调用效率低! public static synchronized SingletonDemo6 getInstance(){ if(instance==null){ instance = new SingletonDemo6(); } return instance; } }
我们使用代码
package com.bjsxt.singleton; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; /** * 测试反射和反序列化破解单例模式 * @author 尚学堂高淇 www.sxt.cn * */ public class Client2 { public static void main(String[] args) throws Exception { SingletonDemo6 s1 = SingletonDemo6.getInstance(); SingletonDemo6 s2 = SingletonDemo6.getInstance(); System.out.println(s1); System.out.println(s2); //通过反射的方式直接调用私有构造器 // Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.bjsxt.singleton.SingletonDemo6"); // Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null); // c.setAccessible(true); // SingletonDemo6 s3 = c.newInstance(); // SingletonDemo6 s4 = c.newInstance(); // System.out.println(s3); // System.out.println(s4); //通过反序列化的方式构造多个对象 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")); SingletonDemo6 s3 = (SingletonDemo6) ois.readObject(); System.out.println(s3); } }
打印出来的s1和s3打印出来的地址是不一样的
如何解决上面的问题的了
package com.bjsxt.singleton; import java.io.ObjectStreamException; import java.io.Serializable; /** * 测试懒汉式单例模式(如何防止反射和反序列化漏洞) * @author 尚学堂高淇 www.sxt.cn * */ public class SingletonDemo6 implements Serializable { //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。 private static SingletonDemo6 instance; private SingletonDemo6(){ //私有化构造器 if(instance!=null){ throw new RuntimeException(); } } //方法同步,调用效率低! public static synchronized SingletonDemo6 getInstance(){ if(instance==null){ instance = new SingletonDemo6(); } return instance; } //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象! private Object readResolve() throws ObjectStreamException { return instance; } }
加入上面的函数式readResolve就可以解决上面的问题
为了能在序列化过程仍能保持单例的特性,可以在Person类中添加一个readResolve()方法,在该方法中直接返回Person的单例对象
实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
package com.bjsxt.singleton; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.util.concurrent.CountDownLatch; /** * 测试多线程环境下五种创建单例模式的效率 * @author 尚学堂高淇 www.sxt.cn * */ public class Client3 { public static void main(String[] args) throws Exception { long start = System.currentTimeMillis(); int threadNum = 10; final CountDownLatch countDownLatch = new CountDownLatch(threadNum); for(int i=0;i<threadNum;i++){ new Thread(new Runnable() { @Override public void run() { for(int i=0;i<1000000;i++){ // Object o = SingletonDemo4.getInstance(); Object o = SingletonDemo5.INSTANCE; } countDownLatch.countDown(); } }).start(); } countDownLatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行! long end = System.currentTimeMillis(); System.out.println("总耗时:"+(end-start)); } }