面向对象的程序设计中,有时候,我们需要一些对象只有一个,比如线程池(threadpool),缓存(cache),对话框,等等。这些示例也只能有一个,如果有很多个,就会造成数据不统一,资源使用过量的结果。
在Java中,使用“单例设计模式”最简单的方法是:在class
中实例化一个对象;将构造函数私有化(没有公开的构造函数,用户就不能再创建另一个对象了);提供通过class
获得对象的方法(static)。
另外,如果这个对象比较耗资源,我们在程序中又不一定会用到它的话,那就太浪费了。所以一般我们会在程序用到单例的时候才第一次实例化这个对象。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Singleton { private static Singleton uniqueInstance; private Singleton(){} public static Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } } |
上面这种实现的方法可能存在的问题是:如果多线程访问,那么对象还没有被实例化之前,可能会有两个线程同时进入getInstance()
方法,一下子出来两个对象。
只要对这个方法加上synchronized
关键字,就可以轻易地解决这个问题了。synchronized
关键字可以保证,每个线程在进入这个方法之前,都必须等其他进程退出。也就是说,不可能同时有两个线程进入这个方法。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Singleton { private static Singleton uniqueInstance; private Singleton(){} public static synchronized Singleton getInstance(){ if(uniqueInstance == null){ uniqueInstance = new Singleton(); } return uniqueInstance; } } |
这样的确可以解决问题,但是又一个问题来了:性能降低。事实上,我们只需要第一次执行这个方法的时候,才需要同步。设置好单例对象,就不需要同步了,之后每次同步都是一种累赘。这个问题,有以下3个解决方法供参考:
1.如果getInstance()
对程序的性能不是很重要,那么……就这样吧,这个方法简单又有效。
2.如果创建示例的代价不是很大,那么可以一开始就创建好示例。这样就可以解决多线程的问题(如此看来,并不是“用到时再创建”就一定好,也要看具体情况,实例化对象代价不大并且需要反复使用,那么就立即创建;如果对象消耗资源大且用到次数不多,再考虑惰性创建)。
1 2 3 4 5 6 7 8 9 |
public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return uniqueInstance; } } |
3. 用双重检查加锁,先检查实例是否创建,如果未创建,才进入同步代码块创建实例。这样的话,只需要进入同步一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; } } |
关于volatile
关键字:volatile
修饰的变量,jvm虚拟机保证从主内存加载到线程工作内存的值是最新的。
在上面的代码中,用到单例的时候,多个线程可以同时检查是否存在对象,如果存在,以后都不用再进入同步代码了。只有第一次实例化的时候,才需要进入同步。即提高了性能,又节省了空间(惰性创建)。
参考资料:《Head First设计模式》