单例模式

来源:互联网 发布:qt tcp编程 编辑:程序博客网 时间:2024/05/18 02:06

单例模式:一个类只能创建一个对象的设计模式

单例模式代码实现

懒汉模式

/* * 要想让一个类只能构建一个对象,则不能让他随便进行new,因此构造方法是私有的 * 懒汉模式(如果单例初始值为null,则构建单例对象) */public class Singleton {    //私有构造函数    private Singleton()    {    }    //单例对象    private static Singleton instance = null;    //静态工厂方法    public static Singleton getInstance()    {        if(instance == null)        {            instance = new Singleton();        }           return instance;    }}

以上创建单例模式的方法是非线程安全的

假设Singleton类刚刚被初始化,instance对象还是空,这时候有两个线程A和B同时访问getInstance()方法。因为instance为空,所以两个线程都通过了条件判断,开始执行 new new Singleton()操作,致使instance被创建了两次。

饿汉模式

/* * 饿汉模式:单例对象一开始就被new Singleton1()主动创建,不再进行判空操作 */public class Singleton1 {    //私有构造函数    private Singleton1()    {    }    //单例对象    private static Singleton1 instance = new Singleton1();    //静态工厂方法    public static Singleton1 getInstance()    {        return instance;    }}

线程安全的单例模式

为了实现线程安全,在懒汉模式上我们进行改进,即防止new Singleton()被执行多次,这里可以使用Synchronized来进行同步。

public class Singleton2 {    //私有构造函数    private Singleton2()    {    }    //单例对象    private static Singleton2 instance = null;    //静态工厂方法    public static Singleton2 getInstance()    {        //双重检测        if(instance == null)        {            //使用同步锁,锁住整个类            synchronized (Singleton2.class) {                 if(instance==null)                {                    instance = new Singleton2();                }            }        }        return instance;    }}

上面这种写法事实还是存在一定问题的
JVM编译器会对指令进行重排

instance=new Singleton()
其实在执行时分三步:
1、首先分配对象的内存空间
2、初始化对象
3、设置instance指向刚分配的内存地址

但是并不是一定按照1,2,3的顺序进行执行,CPU和JVM可能会对指令进行重排。
假设按照1,3,2的顺序进行执行。则线程A执行完1,3后,instance对象并未进行初始化,但是它已经不再指向null,此时如果线程B执行if判断,则结果是false,直接return instance,这里反悔了一个没有初始化完成的instance对象。

为了避免这种情况的发送,即避免指令重排,我们可以在instance对象前加上修饰符volatile
volatile阻止了变量访问前后的指令重拍,保证了指令执行顺序。
volatile在多处理器开发中保证了共享变量的可见性。
由于现在的操作系统,都有缓存机制。当一个线程执行时,它会维护一个私有的堆栈,与公共堆栈中的数据并不是时刻保持一致的。正是由于缓冲区和内存区数据的不一致性,导致程序执行结果出现差别。volatile通过内存屏障,强制从公共堆栈中取得变量的值。
内存屏障指令在多核处理器下会引发两件事:
将当前处理器缓存行的数据写会到系统内存
这个写会内存的操作会使其他CPU里缓存了该内存地址的数据无效。

public class Singleton3 {    //私有构造函数    private Singleton2()    {    }    //单例对象    private volatile static Singleton2 instance = null;    //静态工厂方法    public static Singleton2 getInstance()    {        //双重检测        if(instance == null)        {            //使用同步锁,锁住整个类            synchronized (Singleton2.class) {                 if(instance==null)                {                    instance = new Singleton2();                }            }        }        return instance;    }}

静态内部类实现

public class Singleton3 {    private Singleton3(){}    private static class LazyHolder{        private static final Singleton3 INSTANCE = new Singleton3();    }    public static Singleton3 getInstance()    {        return LazyHolder.INSTANCE;    }}

从外部是无法访问静态内部类LazyHolder的 ,只有调用Singleton.getInstance()方法,才能得到单例对象INSTANCE。这是种懒加载的方式,只有执行Singleton.getInstance(),INSTANCE对象才被初始化。

以上的方式都有一个问题,就是无法防止利用反射来重新构建对象
先来看一下如何利用反射打破单例的约束

public class BreakSingletonByReflect {    public static void main(String[] args) throws Exception{        //先获取单例类的构造器        Constructor<Singleton3> con = Singleton3.class.getDeclaredConstructor();        //设置改构造器为可访问        con.setAccessible(true);        //构造两个不同的对象        Singleton3 singleton1 = con.newInstance();        Singleton3 singleton2 = con.newInstance();        System.out.println("两个对象是否相等"+singleton1.equals(singleton2));    }}

输出结果是:两个对象是否相等false
如何防止反射来重建单例对象?使用枚举的方式

用枚举实现

public enum SingletonEnum{    INSTANCE;    private SingletonEnum()    {        ....        ....        ....    }}

参考http://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA