设计模式之单例模式

来源:互联网 发布:什么是好的相声知乎 编辑:程序博客网 时间:2024/04/30 18:38

网上有那么多的资料,我们为什么还要费时费力的去写博客呢?我想应该是要写出自己本身对一个问题的看法,否则真的没必要写,

直接转载别人的就可以了。以前总是有点应付的感觉,对自己的这样的行为真的很惭愧。话不多说,直入正题。

单例模式也是被用的次数最多的模式之一,接下来我们说下单例模式最简单的一种写法:

class Singleton1{private static Singleton1 singleton1=new Singleton1();private Singleton1(){}public static Singleton1 getInstance(){return singleton1;}}

这并不是单例模式的标准形式,可以根据自己需要来写。从上面不难看出:

1:一个私有的方法,防止对象再次被实例化。

2:一个静态的方法返回一个被实例化的对象,用来执行一些方法。注意这个方法是静态的哦,否则就需要实例化去执行这个方法,而

单例模式是不能再次实例化的。

为什么叫单例模式呢?

这大概是因为在程序运行的过程中,保证只有一个实例存在。

上述形式虽然完成了我们的需要,但从另一个角度上来说,如果我们访问了这个类的任何一个其他的静态域,那么会出现什么后果呢?

这就会导致所有的静态的变量被加载,这可能导致我们也许从来就没有使用过这个变量,白白浪费资源。

既然可能白白的浪费资源,那么就对它稍作调整。

class Singleton1{private static Singleton1 singleton1=null;private Singleton1(){}public static Singleton1 getInstance(){if(singleton1==null){singleton1=new Singleton1();}return singleton1;}}

也许你觉得这已经很完美了,但是,对于考虑并发的情况下,这种情况还真的实用么?

首先我们需要说的是java实例化一个对象的步骤:

1:分配内存空间

2:初始化构造器

3:将内存地址指向分配的内存空间

这是一个jvm初始化对象的过程,2,3的部分的顺序是可能会改变的,毕竟设计jvm的时候会考虑到优化,而优化算法是会对这个过程的

指令进行理论上的改变,以达到期望的效率。

这就让我们想到上面的问题?

假如有100个人进行登录操作,调用配置文件读取,也就是对单例的访问,我们能够保证只有一个实例被创建了么?

下面就简单说一下:

for(int i=0;i<100;i++){    new Runnable() {                        public void run() {            Singleton1.getInstance();        }    };                }

现在我们第一个线程来了,检测到singleton1为null,进行创建类创建实例,然而在创建实例未完成之前,第二个线程来了,然而他依然检测到singleton1为null值

在这种情况下,我们不能够保证只有一个实例被创建出来,那么我们的单例就出错了。

既然这样写不是线程安全的,那么我们将要面对这种情况怎么办?

首先,大家想到的是进行加锁,进行同步代码块,毕竟锁机制本来的目的是为了同步代码而来的。

然后我们就有了另一种写法:

class Singleton1{private static Singleton1 singleton1=null;private Singleton1(){}public static synchronized Singleton1 getInstance(){if(singleton1==null){singleton1=new Singleton1();}return singleton1;}}

这种方法的弊端显而易见,我们要是有有1000个线程,不能老是等一个线程全部判断完吧,这样我们的效率会低到一个无法接受的地步吧

所以这种写法我们不能接受,我们就有了另一种写法:

public static synchronized Singleton1 getInstance(){if(singleton1==null){synchronized(Singleton1.class){if(singleton1==null){singleton1=new Singleton1();}}}return singleton1;}

第一次判断是否为null值是可以理解的,但是为什么还要判断null值呢?

这就要看我们的过程呢?我们的线程1,2,3,线程1,2都执行到了第一次判断null之后,接着线程1去实例化对象,实例化完了,线程2进去了,然而线程2不知道

我们已经实例化完了,他会一股脑再进行另一次实例化,而线程3反而可以直接判断不为null,直接跳过加锁的同步化代码,这样我们即提升了效率又避免了错误,这样很好

我也是这么认为为的,但是我们前面说了jvm的实例化对象的过程,在这种情况下,我们真的能保证不会有问题么?

如果jvm的实例化对象和1,2,3的步骤顺序一致,那么这样是对的,但是我们不能保证是这个顺序,也许顺序变成了1,3,2,如果顺序是1,3,2的情况,那么会导致

什么样的状况呢?

这可能导致我们提前将栈中的地址指向了堆中的对象,就会导致等待的线程2误认为线程1已经实例化对象完成,然而并没有进行初始化构造的过程,直接导致了我们的线程2返回的是没有初始化构造的对象。这时我们会问,难道就没有理论上没有问题的单例么?这也太操蛋了吧。

当然有了,如果没有,我们的单例模式还有意义?

下面就说下我们的最终模式:

public class Singleton {  private Singleton(){}  public static Singleton getInstance(){ return SingletonInstance.singleton;  }  private static class SingletonInstance{       protected static Singleton singleton = new Singleton();   } }

利用内部类的构造,我们能规避上述的问题,既能解决了效率问题,又能同步代码问题,那么难道我们用加锁机制不能完成最终操作么?

这个当然是能的,我们能考虑到的问题,java的开发者当然能考虑到,在jdk1.5之后就有了volatile,这个功能也相当于取消了jvm的实例化时指令优化取消,只要我们在对象上加上这个关键字,那么我们的实例化操作的1,2,3就会按照这个顺序被强迫执行完,不过在jdk1.5以后才有用。

这篇文章想了很久,也参考了很多资料,对我的许多理解帮助甚大,希望对正在学习的人有帮助。当然有什么错误也请和我说,技术的路上达者为师。






1 0
原创粉丝点击