设计模式之单例模式的实现

    Java的23种设计模式中,第一种,也是最基础的一种设计模式就是单例模式,在面试中常常会被考到,下面我们开始简单地实现一下几种单例模式的写法。

    首先,单例模式需要满足的几个条件:一、只能通过单例类获取实例;二、只能获取一个实例;

    根据以上条件,我们很容易就可以写出一个简单的单例实现:

public class Single {
	private static Single single = new Single();
	private Single()
	{}
	public static Single getIntance()
	{
		return single;
	}
	public void say()
	{
		System.out.println("single");
	}
}

    先私有化无参数构造方法,由此我们就可以防止其他对象不通过单例模式私自获取实例化;再写一个静态的单例类,由此可以保证该单例类只会有一个实例,配套的写了一个静态方法,直接返回实例,完成最简单的单例模式。如此简单粗暴的写法叫做饿汉写法,为什么叫做饿汉呢?因为,一旦我们加载了该类,单例类中的单例对象就会被实例化,没有lazyloading的效果。

    由上面改进,我们还可以写一个懒汉写法:

public class Single {
	private static Single single;
	private Single()
	{}
	public static Single getIntance()
	{
		if(single == null)
		{
			single = new Single();
		}
		return single;
	}
	public void say()
	{
		System.out.println("single");
	}
}

    上诉代码的变化主要体现在,单例的对象不会在类加载初期便被实例化,而是在调用获取实例化的静态方法时才会获取实例化,达到lazyloading的效果。代码上的变动主要体现在:成员变量没有进行实例化,获取实例的静态方法里加了一个是否为null的判断,其他大体无差,对应着饿汉方法,称其为懒汉方法。为什么叫做懒汉呢?因为该方式总是在调用getInstance()方法时才会加载实例,表现得很“懒”。

    懒汉方法又存在着一个缺陷,因为饱汉方法总是在类加载初期就拥有了实例化的单例对象,所以“天生的”线程安全,但是懒汉方法就存在线程缺陷,在多线程环境下,如果同时获取了该对象,进行非空判断,很有可能会new两次,获取两个实例化对象。

    于是,我们需要写一个线程安全版本的懒汉模式,这个倒也简单,只要在方法上加上同步锁关键字就行了:

public class Single {
	private static Single single;
	private Single()
	{}
	public static synchronized Single getIntance()
	{
		if(single == null)
		{
			single = new Single();
		}
		return single;
	}
	public void say()
	{
		System.out.println("single");
	}
}

    ----------2018-06-04更新

    饿汉模式是天生具有线程安全性的,因为饿汉模式是在类加载的初始化阶段就把实例化对象引用交给了single。在上面关于,线程安全的懒汉模式只写了一种,但是实际上有五种实现方式:

    1、如上一种;

    2、将synchronized关键字改为修饰代码块(作用不大);

    3、内部静态类实现,写一个内部静态类,在其中使用饿汉方法,然后返回内部静态的静态成员变量,同样也是lazy加载;

    4、double-check方法,实现思路是:先判断返回引用是否为空,为空则加锁,不为空则直接返回引用,大大地提高了效率,但是存有一个问题。single = new Single()并不是一个原子化的操作,他可以分为三个步骤:

        1)在内存中分配一块内存;

        2)将内存初始化;

        3)最后将引用指向内存;

    但是当指令重排时,很可能就不是如上的步骤了,有可能是132。

    如此一来假使线程1执行到指令3时,但是未执行指令2,这时线程2执行到外层的非空判断,发现引用不为空,直接跳过first check,返回了一个未初始化的实例,这时,就存在问题了。

    所以,使用volatile关键字修饰该静态变量,禁止指令重排序,完美解决问题,代码如下:

//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
    private static volatile Singleton3 singleton3;

    private Singleton3() {
    }

    public static Singleton3 getSingleton3() {
        // Double-Check idiom
        if (singleton3 == null) {
            synchronized (Singleton3.class) {       // 1
                // 只需在第一次创建实例时才同步
                if (singleton3 == null) {       // 2
                    singleton3 = new Singleton3();      // 3
                }
            }
        }
        return singleton3;
    }
    5、最后,还有一个方式,使用ThreadLocal实现懒汉,附上我的ThreadLocal的博客 ThreadLocal深入浅出,代码如下:
// ThreadLocal 线程局部变量,将单例instance线程私有化
    private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>();
    private static Singleton instance;

    private Singleton() {

    }

    public static Singleton getInstance() {

        // 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
        if (threadlocal.get() == null) {
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查:该单例是否被创建
                    instance = new Singleton();
                }
            }
            threadlocal.set(instance); // 将单例放入ThreadLocal中
        }
        return threadlocal.get();
    }
    有点多此一举的意思,还不如直接饿汉。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值