单例模式

来源:互联网 发布:本地端口1080 编辑:程序博客网 时间:2024/04/30 06:19

单例模式常见的三种实现方式:

1.懒汉式

为什么称之为懒汉式,因为这种方式很“懒”,只有当别人向它请求一个对象的时候,它才会产生一个对象供别人使用:

package DesignMode;public class SingletonMode{    private static SingletonMode single = null;    public static SingletonMode getInstance()    {        if(single == null)        {            single = new SingletonMode();            return single;        }else{            return single;        }    }}

以上代码有个问题,那就是在多线程的情况下仍然有可能会产生多个实例,因此就有如下改进:

①:改进一

package DesignMode;public class SingletonMode{    private static SingletonMode single = null;    //方法上加synchronized关键字    public static synchronized SingletonMode getInstance()    {        if(single == null)        {            single = new SingletonMode();            return single;        }else{            return single;        }    }}

加了synchronized关键字防止线程并发引发产生多个实例。但是这样会产生2个问题,一个就是性能瓶颈问题:每次获得对象实例的时候都要对字节码加锁,从而影响程序性能;第二个问题:我们称第二个问题为“红色巨人”(这个昵称是我自己起的…后面我们会再次提到这个问题,我会解释他)。

首先为了解决上述问题,我们产生了改进型二。

②:改进二

package DesignMode;public class SingletonMode{    private static SingletonMode single = null;    public static SingletonMode getInstance()    {        if (single == null)        {            //锁的范围缩小            synchronized (SingletonMode.class)            {                if (single == null)                {                    single = new SingletonMode();                }            }        }        return single;    }}

如上优化之后,在并发环境下,某些线程就会不必进入同步块,因此省去了加锁的性能开销。但是我们要注意,“改进二”的优化是错误的!我们上面提到过“红色巨人”这个问题,关键就在这里:

下面是针对红色巨人问题的改进:

③:改进三

package DesignMode;public class SingletonMode{    private static volatile SingletonMode single = null;    public static SingletonMode getInstance()    {        if (single == null)        {            //锁的范围缩小            synchronized (SingletonMode.class)            {                if (single == null)                {                    single = new SingletonMode();                }            }        }        return single;    }}

这里我们只是加了一个volatile关键字。

下面我来解释一下为什么需要加volatile关键字。

首先我来简要说一下volatile关键字的作用,volatile可以保证变量的内存可见性,具体是通过两种方式保证的(加入内存屏障和禁止重排序优化)。具体可以参考JAVA并发机制的底层实现原理。

我们知道,在JVM装载类的时候,第三个阶段,也就是准备阶段(具体可以参考我的这篇博客),会将申请到的内存空间进行初始化,具体可以用下面的三行伪代码进行解释:

memory = allocate();  //1.分配对象内存空间ctorInstance(memory);   //2.初始化这块空间instance = memory;      //3.设置instance指向刚刚分配的这块地址

但是在某些(JIT编译器上),可能会发生重排序,排序之后如下:

memory = allocate();  //1.分配对象内存空间instance = memory;      //2.设置instance指向刚刚分配的这块地址ctorInstance(memory);   //3..初始化这块空间

假设重排序之后如上,当执行完第2步之后,如果恰好有另外一个线程请求单例对象,那么对这个对象的检测肯定不会为null,因此会返回一个instance对象,但是instance对象指向的内存空间还未执行第三步,即未初始化,因此如果该线程继续使用这个instance对象,就会发生问题。

0 0
原创粉丝点击