单例模式确保此类仅有一个实例,自行实例化并提供一个访问它的全局公有静态方法。
通常两种情况下需要用到单例模式:
- 实例话某个对象要消耗过多资源,避免频繁创建和销毁对象对资源的浪费。(如:对数据库操作,访问IO,网络请求等)
- 某种类型对象应该只存在一个,如果产生过多实例,会导致程序行为异常和结果不一致。(如:一个管理系统,缓存等)
单例模式的优缺点:
- 优点:可以减少系统内存开支和性能开销,避免对资源的占用。
- 缺点:不容易扩展,容易引发内存泄漏。
单例的实现方式:
- 按加载时机来:懒汉式和饿汉式。
- 按实现方式来:双重检查加锁,内部类,枚举等。
最简单的懒汉式
|
|
这种写法只能在单线程下使用。如果是多线程,可能发生一个线程通过进入 if(mInstance == null) 判断语句块,但还未来得及创建新的实例时,另外一个线程也通过了这个判断,最终两个线程都进行了创建实例,导致多个实例的产生。所以多线程下不要使用这个方法。
对getInstance方法加上sychronized的懒汉式
|
|
对于这种写法的懒汉式,会延迟加载,在第一次调用的时候才会初始化。如果不需要同步的情况下,效率比较低。而事实上实例创建完成后,同步就变为不必要的开销了,这样做在高并发下肯定会影响性能。
2.饿汉式
|
|
对于饿汉式,类加载的时候单例就会实例化,会浪费内存。
3.双重检查锁定(Double Check Lock)
|
|
在JDK1.5及之后,这种方法才能达到单例效果。
此方法的“Double-Check”体现在进行了两次 if (mInstance == null) 的检查,这样既同步代码块保证了线程安全,同时实例化的代码也只会执行一次,实例化后同步操作不会再被执行,从而效率提升很多。
为什么要用volatile来修饰mInstance?
因为mInstance = new Singleton();并非是一个原子操作,实际上JVM在这句话上做了大概3个操作:
1.给mInstance分配内存
2.调用Singleton的构造函数来初始化成员变量
3.将mInstance对象指向分配的内存空间(执行完这步mInstance就是非null了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
4.静态内部类
|
|
静态内部类只有在第一次调用的时候才会去加载类。同时实现延迟加载和线程安全。