单例模式,独一无二的对象,提供一个全局访问点。
1. 非多线程下使用。
/**
* 单例模式,提供全局访问点
*
* @author fuzhengchao
*
*/
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
// 一些初始化操作
}
public static Singleton getInstance() {
if (null == uniqueInstance) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
由getInstance()方法的实现我们可以看出,单例模式还有延迟实例化的作用,在第一次需要的时候才创建对象。
但是这种实现方法在多线程的时候就有问题了,当多个线程同时进入if (null == uniqueInstance) 和uniqueInstance = new Singleton();
之间时,就会创建多个对象,这就违背了单例模式的设计初衷了。
2. 多线程下单例模式的实现。
2.1 如果多线程线程数不是很多,程序对性能要求不是很高,我们可以这样设计:
/**
* 单例模式,提供全局访问点
*
* @author fuzhengchao
*
*/
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
// 一些初始化操作
}
// 加锁
public static synchronized Singleton getInstance() {
if (null == uniqueInstance) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
把getInstance()加锁,这样就能保证多个线程不能同时进入创建多个对象了。 但为什么这样做会影响程序性能呢?如果线程过多,每次调用getInstance()方法时,只能一个线程一个线程进入,那么这里就会造成很多线程等待同步,从而影响性能。
2.2 使用“急切”创建实例,而不用延迟实例化的做法。
/**
* 单例模式,提供全局访问点
*
* @author fuzhengchao
*
*/
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
// 一些初始化操作
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
从上面的代码我们可以看出,我们取消了加锁操作,直接在静态初始化中创建了对象,我们依赖JVM在加载这个类是创建了唯一的单例,后面不会在创建了。这样再多的线 程同时操作也不会出现等待同步影响性能了,但是这样新问题又出现了,我们的单例不能延迟实例化了,程序一开始就创建了这个对象一直到程序退出,如果中间运用这个 单例在程序很靠后,或则很少情况下,这样就比较浪费资源了。
2.3 用“双重检测加锁”,在getInstance()中减少使用同步
/**
* 单例模式,提供全局访问点
*
* @author fuzhengchao
*
*/
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
// 一些初始化操作
}
// 加锁
public static Singleton getInstance() {
if (null == uniqueInstance) {
synchronized ((Singleton.class)) {
if (null == uniqueInstance) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
这样只有在第一次创建对象时,多个线程同时进入第一个null == uniqueInstance和synchronizede()之间时,一个线程进去创建对象,其它线程获取对象会出现等待同步。只要对象一旦创建成功,后续的线程都直接在第一个null == unigueInstance 退出,后面就不会有性能问题了。但是已经进入null == uniqueInstance 和synchronized之间的线程还得等待同步。