第一篇文章:单例模式的6种写法

昨天收到了上个月的工资,才发觉一转眼从sohu北研离职已经一个月了。为了专心在学校做研究,下决心辞掉了实习,其实现在想起来每周多出来的几天时间利用率也没高到哪里去。突然想到在实习的时候,有一次听说他们面试实习是的题目是写单例模式,我不以为然的说,这么简单的东西谁没学过,根本没有区分度嘛。谁知道鹏鹏悠悠的说,你知道单例模式的6种写法么……我惊呆了,Google一下,才知道自己之前学过的写法只是最简单的一种,几乎没有任何价值。

说来惭愧一大把年纪了还没有个像样的技术blog,于是今天决定重新复习总结一下单例模式的几种写法,作为博客的第一篇文章吧!


首先简单的介绍下单例(Singleton)模式。

Singleton模式是软件工程中最有名的模式之一。简单来说,一个类如果使用了Singleton模式,那么该类中只有一个实例而且该实例能够容易的被外界访问。通常,创建实例时不允许指定参数,否则如果使用不同的参数第二次对实例进行获取时可能会有问题 (如果需要使用相同的参数访问对应同一个实例,那么工厂模式是比较合适的。)由于创建一个实例有可能需要消耗大量资源(比如数据库连接),我们通常使用懒汉(lazy)方式创建单例,即:直到需要使用时才进行实例的创建。


第一种方式最常见的单例模式示例。通常它只作为Singleton思想的简单示范,而很少投入使用。碰巧它也被用在《Java并发编程实战》中,作为非线程安全的例子。注意其中第6行的条件判断,它在这里成为了一个“竞态条件”,破坏这个类的正确性。简单来说,竞态条件的本质是:基于一种可能失效的观察结果来作出判断或者执行某个计算。我们可以想象当两个线程A、B执行到第6行时,A判断instance为null,因为对instance进行初始化。而B同样需要判断此时的instance是否为null,这时的结果取决于“不可预测的时序,包括线程的调度方式,以及A需要花多长时间来初始化instance。”所以如果B判断的结果是instance为null,那么将会再次对instance进行初始化,这使得线程A、B调用同一个方法返回了不同的结果。

特点:1、非线程安全。2、懒汉方式创建

Java代码如下:

public class Singleton {
    private static Singleton instance;
    private Singleton (){}

    public static Singleton getInstance() {
	if (instance == null) {
	    instance = new Singleton();
	}
	return instance;
    }
}


第二种方式使用synchronized关键字来修饰getInstance()方法,因此在同一时刻只有一个线程可以执行getInstance()方法。然而,这种使得每次getInstance()方法被调用时,都产生额外的开销,过于极端,效率很低。

特点:1、线程安全。2、懒汉方式创建

Java代码如下:

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


第三种方式:第二种方式的缺陷是对整个getInstance()方法进行了syncrhonized修饰,其实需要修饰的只有第六行初始化instance的部分。对第二种方式进行改进,使用双重校验锁。其思想很简单:当有多个线程对第五行的条件检测为true,等待进入syncrhonized代码块时,必然有第一个线程执行完syncrhonized代码块中的步骤,完成第8行的初始化实例;而之后的线程在进入syncrhonized代码块时,第二次条件检测使得它们不会再重新创造实例。

特点:1、线程安全。2、懒汉方式创建

Java代码如下:

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getInstance() {
	if (singleton == null) {
	    synchronized (Singleton.class) {
		if (singleton == null) {
		    singleton = new Singleton();
		}
	    }
	}
	return singleton;
    }
}


第四种方式:与第一种方式相比,该方式去掉了竞态条件,基于classloader机制保证初始化instance时只有一个线程,避免了多线程的同步问题。但由于instance在Singleton类装载时就实例化,而我们无法确保调用getInstance方法之前类没有通过其他方式装载, 这使得懒汉(lazy)方式创建实例的条件无法被满足。

特点:1、线程安全。2、饿汉方式创建

Java代码如下:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
	return instance;
    }
}


第五种方式:巧妙的运用了一个内部静态类SingletonHolder,在SingletonHolder类中进行instance的初始化。与第三种方式相比,不用考虑Singleton类之前会被哪些方式初始化,而只有调用getInstance()方法时,才会初始化SingletonHolder类,从而实例化instance。

特点:1、线程安全。2、懒汉方式创建

Java代码如下:

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


第六种方式:在java 5或以上的版本中,可以使用枚举(enum)类型来实现单例模式。枚举是线程安全的。

特点:1、线程安全。2、懒汉方式创建

Java代码如下:

public enum Singleton {
    INSTANCE;  
    private Singleton() {
        System.out.println("Here");
    }
}

public static void main(String[] args) {
    Singleton.INSTANCE;
}

实际上,将Singleton声明为enum之后,会被编译为:

public final class Singleton {
    public final static Singleton INSTANCE = new Singleton(); 
}


参考资料:

1、Implementing the Singleton Pattern in C#

2、单例模式的七种写法

3、Singleton Design Pattern – An Introspection W Best Practices

4、《Java并发编程实战》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值