26种设计模式中我们接触的最多的应该就是单例模式了,单例顾名思义就是一个类只有一个实例存在。
单例模式可以分为5种,以前我是只知道懒汉式和饿汉式,今天看了个博客,知道了另3种:静态内部类的形式,枚举类的形式(推荐使用),双重校验锁的形式。
我以前对于单例的用法,只是考虑把构造方法私有化,没有考虑到多线程的情况,一般是这样写:
- public class Singleton {
- private static Singleton singleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (null == singleton) {
- singleton = new Singleton();
- }
- return singleton;
- }
- }
- public class Singleton {
- private static Singleton singleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- synchronized (Singleton.class) {
- if (null == singleton) {
- singleton = new Singleton();
- }
- return singleton;
- }
- }
- }
这样写依然存在性能问题,就是每次判断它是否为空的时候都要执行同步代码块。怎么解决这个问题,就形成了双重校验锁的雏形。我们判断两次是否为空,第二次不为空的时候再去创建实例,这样就解决了性能的问题:
- public class Singleton {
- private static Singleton singleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (null == singleton) {
- synchronized (Singleton.class) {
- if (null == singleton)
- singleton = new Singleton();
- }
- }
- return singleton;
- }
- }
这里我觉得是没问题了,但问题没那么简单,没遇到过的东西多少会考虑没那么周全。当然这个问题是别人的博文里提出的,我觉得虽然遇到的可能性很小,但也不能不考虑。
这样写存在一个多处理器创建的时候的问题。一个类被创建的时候是需要一定时间的,cup1判断变量为空,然后它去实例化这个类,可是在实例的过程中还没有完成的时候,cup2需要使用这个类,它去判断的时候发现这个类已经被cup1实例化了,于是它就直接使用,但是cup1实例化还没有完成,这个类还是一个半成品,这个时候cup2使用必定会出现一些莫名其妙的问题,如何解决这个问题呢?好在有一个关键字volatile(这个关键字我还真不知道什么意思,看到这里我又baidu了一下,具体解释写在最后,不懂的可以先看看方便理解),这个就是用来解决多处理器变量共享的问题,于是一个完美的双重校验锁应该这样写:
- public class Singleton {
- private volatile static Singleton singleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (null == singleton) {
- synchronized (Singleton.class) {
- if (null == singleton)
- singleton = new Singleton();
- }
- }
- return singleton;
- }
- }
至此,我觉得是懒汉式升级成了双重校验锁的形式,个人认为这种升级的具体使用还是要看情况,并不是每个单例都要这么做吧。
饿汉式因为本身就不存在多线程的问题,所以不必要考虑多线程,一个标准的写法如下:
- public class Singleton {
- private static Singleton singleton=new Singleton();
- private Singleton() {
- }
- public static Singleton getInstance() {
- return singleton;
- }
- }
- public class Singleton {
- private Singleton() {
- }
- public static Singleton getInstance() {
- return SingletonHolder.SINGLETON;
- }
- private static class SingletonHolder{
- private static Singleton SINGLETON=new Singleton();
- }
- }
这样就可以保证只有在调用getInstance()的时候再去加载内部类,加载内部类的时候再去实例化变量,而这样也很轻松的解决了多线程的问题。
最后一种很不常用但是去推荐使用的形式是枚举的形式(这里我也不知道为什么要推荐),写法如下:
- public enum Singleton {
- INSTANCE;
- private Singleton() {
- }
- public String[] getStrs() {
- final String[] strs = new String[3];
- strs[0] = "AAA";
- strs[1] = "BBB";
- strs[2] = "CCC";
- return strs;
- }
- }
- Singleton.INSTANCE.getStrs()[0];
最后,使用单例模式还是存在一定的问题,比如我们虽然使用private保证了类不能被new出来,可是在java的反射机制中,private是没有作用的,这样就不能保证单例实例的唯一性,再比如在饿汉式中,虽然变量是使用类加载器来实例化能保证唯一性,可是如果存在多个类加载器就无法保证唯一性,所以最好不要随便使用单例要看具体情况,如果真的必须使用,建议使用静态内部类、双重锁、枚举这3种中的一种,优先考虑使用枚举。
这里我附上volatile的解释:
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。
这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。
而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
差点忘了,这里最后贴上博文链接:
http://blog.csdn.net/qq379454816/article/details/50202151 这是我看的单例模式博文原地址
http://blog.sina.com.cn/s/blog_724cc9a60100t66z.html 这是百度的好几篇文章中我觉得比较好理解的volatile的解释的原地址