最近看到一篇关于Java中instance的文章(http://www.zhihu.com/question/29971746),引发对单例模式的一些思考,并从网上搜集了一些关于Java单例模式的文章,总结如下:
首先,贴出三种单例设计的简单代码:
1.延迟加载——不考虑效率问题的延迟加载
public class SingleTon{ private static SingleTon instance = null; public static synchronized SingleTon getInstance(){ if(instance==null){ instance = new SingleTon(); } } }
2.即时加载的基本实现
public class SingleTon(){ private static SingleTon instance = new SingleTon(); public static SingleTon getInstance(){ return instance; } }
3.Double Check方式——关键考虑在于并发环境下返回结果的性能提升能否抵消多出来的两次判断跳转
public class SingleTon{ private volatile static SingleTon uniqueInstance; private SingleTon(){} public static SingleTon getInstance(){ if(uniqueInstance==null){ synchronized(SingleTon.class){ if(uniqueInstance == null){ uniqueInstance = new SingleTon(); } } } return uniqueInstance; } }
分析:
1、第1和第2的方式,只考虑效率问题的话,若不是出现“单例爆炸”的情况(既在系统中存在了大量不同的实例,使用即时加载,这些没用的实例同时加载..造成效率低),延迟加载和即时加载效率上差异不大...
2、第3种DCL(Double Check Lock),两次检查instance==null,第一个为了避免每次都加锁,避免了一定的加锁的开销;
第二个判断为了避免同步的问题,such as:十个线程同时调用getInstance()方法,都执行了第一步检查instance == null,并且其中一个线程成功获得了锁(执行了synchronized语句),接下来如果没有再一次判断instance == null,则十个线程将生成10对象,这违背了单例的初衷,其中DCL对开销的关注在于:使用DoubleCheck方式的主要出发点是”避免同步锁自身的初始化开销”,而不是”避免被锁住 内容的执行开销“, 即需要考虑到加锁的开销和条件判断的开销哪个更大。。
另一个特点是变量instance的类型声明中添加了volatile,因为像下面这种创建对象的语句并不是原子操作,volatile可以使其成为原子操作,避免同步问题