单例模式:单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类有且只有一个实例。
单例模式最初的定义出现于《设计模式》:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
单例模式的特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
首先我们从一个简单的单例模式(饿汉式)开始构建:
饿汉式:
class USBDriver {
private static USBDriver driver = new USBDriver();
private USBDriver() {
}
public static USBDriver getInstance() {
return driver;
}
}
首先什么是饿汉式呢,我们都知道一个饿汉肯定是对事物渴求,也就是对某种资源渴求的。所以我们的饿汉单例模式 也就是对 单例十分的渴求
渴求到什么程度呢,当类装载进来的时候 就立即着手构建我们的实例了。这里private的构造函数,是防治new出来,这样就不是单例了。
这里需要问一个问题,这会不会引起线程安全问题?
答案是不会,因为在USBDriver类初始化时,就已经加载进来了,所以在调用getInstance()时候,并不会产生线程安全问题,可以说天生就是线程安全的。
如果我们的驱动程序比较耗费资源,如果我们可能会用到几十种甚至更多的驱动程序的话,那么我们对于这种饿汉单例模式是不是需要进行一些改进,
使的当我们真正需要该实例的时候才初始化,那么我们什么时候才真正需要该实例呢?就是我们调用getInstance()的时候。
class USBDriver {
private static USBDriver driver = null;
private USBDriver() {
}
public static synchronized USBDriver getInstance() {
if (driver == null) {
driver = new USBDriver();
}
return driver;
}
}
好的,这次我们再一次改进,对于该单例我们采取了懒加载策略(懒汉式),直到需要用的时候才构建。
这里我们给整个getInstance()方法使用了synchronized 来锁定该方法。
这样同样带一个问题,就是因为锁的粒度是方法级的,所以对于多于多线程性能会产生很大的问题。
所以我们可以将锁适当的细化,将锁移至内部,采用更小的锁块来进行优化(双重检查锁定式)。
class USBDriver {
private static volatile USBDriver driver = null;
private USBDriver() {
}
public static USBDriver getInstance() {
if (driver == null) {
synchronized (USBDriver.class) {
if (driver == null) {
driver = new USBDriver();
}
}
}
return driver;
}
}
这里我们在driver 里面加了 volatile 修饰,这是为了保证读和取都是在保证不会被多线程打断,这是一种保护措施,保证获得driver 的最新信息。
为什么要采用双重检查策略呢?考虑一种情况,当我一个线程执行到 if(driver ==null)的时候,这个时候driver还没初始化,如果不采用双重检查的话,我读到了driver =null,但是这个时候有一个线程抢险进行到了同步代码块。
当driver 构建之后,之前的线程因为读到了 driver = null,实际上这个时候driver 并不是null了
这就无异带来了线程安全问题,而且更糟糕的是,可能会产生引用逸出。
即 我一个线程先 调用getInstance获得了一个driver,然后后一个线程产生上述分析的情况,重新new 了一个driver。这个时候就破坏了有且只有一个的特性。
事实上,这种双重检查锁定策略,在ConcurrentHashMap也是有类似的设计的。
那么有没有办法避免同步代码块呢?
是可以的,不过这利用到jvm保证每个类只被加载一次的特性。
在我们的驱动类内增加一个内部内,该内部类持有驱动实例,但是该实例,只有在该类被加载的时候才会真正的初始化。
class USBDriver {
private USBDriver() {
}
private static class USBDriverHolder {
private static USBDriver driver = new USBDriver();
}
public static USBDriver getInstance() {
return USBDriverHolder.driver;
}
}
这种方式称为静态内部类单例,该方法无疑在简洁性,性能上都有很大的优势,但是有个弊端,就是必须对驱动类进行大的结构变动。很多情况下,我们无法改动代码内部源码,
比如引入外部的jar。
看看spring中的单例:
DefaultSingletonBeanRegistry类中的getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
我们可以看到spring中对于单例似乎是用了我们的懒加载的 双重检查锁定。
这里spring 采取双重检查锁定也是有一定原因的。如果使用懒汉式势必带来运行效率的降低,
如果采用静态内部类单例,必须去改动源码。spring 并不会侵入你的代码。