单例模式

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

单例模式代码实现

懒汉模式
/*
 * 要想让一个类只能构建一个对象,则不能让他随便进行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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值