单例模式之坑与爬坑
2016-07-05 17:10
393 查看
上篇简述了其中单例还有部分个人看法,本篇主要从三个问题进行开展
怎么避免线程阻塞
怎么避免内存泄漏
怎么避免被反射
首先先看一段事例代码
这是之前看到的一段代码,这是一个基于okhttp2.x的一个封装。上述代码,有的会把synchronized 加在方法上,其实两种效果一样。通过这段代码,逐步分析上述所说的三个问题。
首先,当同时有多个线程一起访问这个方法的时候因为有锁会一个个执行,未被执行的线程就会被锁在方法外,当并发量过大的时候,如果方法内部是一个很耗时的操作,这就造成了线程阻塞。这就如同过独木桥,对于独木桥的承载,一次只能承载一个人,如果这独木桥很长,那后面的人等待的时间就会很长,便会一直阻塞在独木桥的一端。
对于内存泄漏,有一个很常见的问题,如果一个类中,只要有一个字段被长期占有得不到释放,那么这个类就得不到及时销毁。上述代码中的context就是,当第一个context传过来的时候,这个context被会被长期占有,,如果此类使用足够多的强引用,再点击一些其它页面,很容易产生OOM(OOM主要来源于强引用类型,就算发生OOM,强引用类型的对象也不会被销毁)。在okhttp3.x版本中,对于2.x版本,源码做了很大的改变和调整,而且,不管在2.x版本或是3.x版本中,如果没有特殊需要,不用刻意去配置缓存和空间,源码中都有默认的设置,如果真的想设置缓存路径,可以在application中设置,在上述单例过程中直接传过来,而并不需要传一个context。
改成这样,看起来好像是完美了,有的人会疑问,为什么上锁后的执行代码中还要一次判空操作,这是防止多个线程在singleton 判断为空的过程中都已通过执行,被锁在外面,当一个个执行锁内方法的时候,多次实例化。这个貌似一切顺理成章的代码,背后却有依然还有一个大问题。
最后一个问题,这是我在一次面试过程中遇到的,面试官问我怎么解决通过反射机制拿到私有构造器,其实这个问题之前在《effective Java》上看到过,当时没怎么在意,今天再次翻阅书籍的时候,看到上面写道,“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”对于反射机制调用私有构造器,之前和别人讨论过,有的说在私有构造器中加入一些没必要的字段,我擦,这方法虽然行之有效,但也太傻了吧,有的说如果别人想通过反射去拿,那就让它拿呗,不用管的,一般情况下,大家都是这么做的,这是一个普遍方式,还有的说用抛异常的方式,还有的说用枚举,最后两种方式都是可取的,代码如下:
此处可以看出,第一次用反射成功调用了test,第二次创建实例的时候便会抛出异常。在1.5以后,《effective Java》提倡枚举,示例如下:
由此可见,这种写法也可以防止单例模式被攻击,这种写法在功能上和共有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为Singleton的最佳是方法。
怎么避免线程阻塞
怎么避免内存泄漏
怎么避免被反射
首先先看一段事例代码
public class OkHttpUtils { private static OkHttpClient singleton; private static final int TIME_OUT = 0X00000A; private OkHttpUtils() { } public static OkHttpClient getInstance(Context context) { synchronized (OkHttpUtils.class) { if (singleton == null) { singleton = new OkHttpClient(); File cacheDir = new File(context.getCacheDir(), "qkl"); int cacheSize = 10 * 1024 * 1024; // 10 MiB singleton.setCache(new Cache(cacheDir, cacheSize)); singleton.setConnectTimeout(TIME_OUT, TimeUnit.SECONDS); singleton.setReadTimeout(TIME_OUT, TimeUnit.SECONDS); } return singleton; } } }
这是之前看到的一段代码,这是一个基于okhttp2.x的一个封装。上述代码,有的会把synchronized 加在方法上,其实两种效果一样。通过这段代码,逐步分析上述所说的三个问题。
首先,当同时有多个线程一起访问这个方法的时候因为有锁会一个个执行,未被执行的线程就会被锁在方法外,当并发量过大的时候,如果方法内部是一个很耗时的操作,这就造成了线程阻塞。这就如同过独木桥,对于独木桥的承载,一次只能承载一个人,如果这独木桥很长,那后面的人等待的时间就会很长,便会一直阻塞在独木桥的一端。
对于内存泄漏,有一个很常见的问题,如果一个类中,只要有一个字段被长期占有得不到释放,那么这个类就得不到及时销毁。上述代码中的context就是,当第一个context传过来的时候,这个context被会被长期占有,,如果此类使用足够多的强引用,再点击一些其它页面,很容易产生OOM(OOM主要来源于强引用类型,就算发生OOM,强引用类型的对象也不会被销毁)。在okhttp3.x版本中,对于2.x版本,源码做了很大的改变和调整,而且,不管在2.x版本或是3.x版本中,如果没有特殊需要,不用刻意去配置缓存和空间,源码中都有默认的设置,如果真的想设置缓存路径,可以在application中设置,在上述单例过程中直接传过来,而并不需要传一个context。
public class OkHttpUtils { private static OkHttpClient singleton; private static final int TIME_OUT = 0X00000A; private OkHttpUtils() { } public static OkHttpClient getInstance() { if (singleton == null) { synchronized (OkHttpUtils.class) { if (singleton == null) { singleton = new OkHttpClient(); int cacheSize = 10 * 1024 * 1024; // 10 MiB singleton.setCache(new Cache(ZeroApplication.cacheDir, cacheSize)); singleton.setConnectTimeout(TIME_OUT, TimeUnit.SECONDS); singleton.setReadTimeout(TIME_OUT, TimeUnit.SECONDS); } } return singleton; } } }
改成这样,看起来好像是完美了,有的人会疑问,为什么上锁后的执行代码中还要一次判空操作,这是防止多个线程在singleton 判断为空的过程中都已通过执行,被锁在外面,当一个个执行锁内方法的时候,多次实例化。这个貌似一切顺理成章的代码,背后却有依然还有一个大问题。
最后一个问题,这是我在一次面试过程中遇到的,面试官问我怎么解决通过反射机制拿到私有构造器,其实这个问题之前在《effective Java》上看到过,当时没怎么在意,今天再次翻阅书籍的时候,看到上面写道,“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。”对于反射机制调用私有构造器,之前和别人讨论过,有的说在私有构造器中加入一些没必要的字段,我擦,这方法虽然行之有效,但也太傻了吧,有的说如果别人想通过反射去拿,那就让它拿呗,不用管的,一般情况下,大家都是这么做的,这是一个普遍方式,还有的说用抛异常的方式,还有的说用枚举,最后两种方式都是可取的,代码如下:
/** * created by zero on 2016-07-05 */ public class SingletonTest { public static void main(String[] args) { try { Class<Singleton2> classType = Singleton2.class; Constructor<Singleton2> c = classType.getDeclaredConstructor(); c.setAccessible(true); Singleton2 s1 = (Singleton2)c.newInstance(); System.out.println(s1.test()); Singleton2 s2 = Singleton2.getInstance(); System.out.println(s2.test()); } catch (Exception e) { e.printStackTrace(); } } }
/** * created by zero on 2016-07-05 */ public class Singleton2 { private static boolean flag = false; private Singleton2() { synchronized (Singleton2.class) { if (flag == false) { flag = !flag; } else { throw new RuntimeException("单例模式被侵犯!"); } } } private static class SingletonHolder { private static final Singleton2 INSTANCE = new Singleton2(); } public static Singleton2 getInstance() { return SingletonHolder.INSTANCE; } public String test(){ return "test"; } }
test Exception in thread "main" java.lang.ExceptionInInitializerError at com.zm.zero.test.Singleton2.getInstance(Singleton2.java:29) at com.zm.zero.test.SingletonTest.main(SingletonTest.java:15) Caused by: java.lang.RuntimeException: 单例模式被侵犯! at com.zm.zero.test.Singleton2.<init>(Singleton2.java:18) at com.zm.zero.test.Singleton2.<init>(Singleton2.java:9) at com.zm.zero.test.Singleton2$SingletonHolder.<clinit>(Singleton2.java:25) ... 2 more
此处可以看出,第一次用反射成功调用了test,第二次创建实例的时候便会抛出异常。在1.5以后,《effective Java》提倡枚举,示例如下:
/** * created by zero on 2016-07-05 */ public class SingletonTest { public static void main(String[] args) { try { Class<Singleton3> classType = Singleton3.class; Constructor<Singleton3> c = (Constructor<Singleton3>) classType .getDeclaredConstructor(); c.setAccessible(true); c.newInstance(); } catch (Exception e) { e.printStackTrace(); } } }
public enum Singleton3 { INSTANCE; public void test() { System.out.println("test"); } }
java.lang.NoSuchMethodException: com.zm.zero.test.Singleton3.<init>() at java.lang.Class.getConstructor0(Unknown Source) at java.lang.Class.getDeclaredConstructor(Unknown Source) at com.zm.zero.test.SingletonTest.main(SingletonTest.java:15)
由此可见,这种写法也可以防止单例模式被攻击,这种写法在功能上和共有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为Singleton的最佳是方法。
相关文章推荐
- 文章标题
- Java static关键字以及Java静态变量和静态方法
- 【数据结构与算法】Hash Table
- UVa 1025 A Spy in the Metro
- 342. Power of Four
- Entity Framework Code-First(17):Database Initialization Strategy
- Android动态加载启动页
- U3D报错:Screen position out of view frustum
- iOS 为什么选择了OC
- Tomcat日志catalina.out分割
- angular学习(四)—— Services
- 关于Spring Data redis几种对象序列化的比较
- unity3d小常识
- Android热修复---AndFix
- HTML5画布(CANVAS)速查简表
- oracle游标全解2
- logstash parse mysql log
- ajax跨域问题
- sql获取的时间不能直接在c#中tostring成我们要的格式,要转化一下
- 分布式基础学习