单例模式在实际开发过程中经常会用到,我们有必要充分的理解单例模式。单例模式有多种写法,分为懒汉式、饿汉式、双重锁等。
单例模式的定义:
- 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式的好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
饿汉式:
public class Singleton1 {
//私有的默认构造子
private Singleton() {}
//已经自行实例化
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
- 当这个类被加载的时候,new Singleton() 这句话就会被执行,就算是getInstance()没有被调用,类也被初始化了。但是,我们希望他能在我第一次getInstance()时才被真正的创建。这样,我们可以控制真正的类创建的时刻,而不是把类的创建委托给了类装载器。
懒汉式(一):
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 仅能用于单线程中,在多线程中不能正常工作。
懒汉式(二):
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 可在多线程中很好的工作。
静态内部类:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
- 这种方式在《Effective Java》上推荐使用。这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
双重校验锁:
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
- volatile 禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。只能用于JDK 1.5及以上,在JDK1.5以前不能使用。
总结:
- 《懒汉式(二)》很常见,但是一般会偏向使用《静态内部类》的方式。
- 《双重校验锁》方式也很好,但是它会受到JDK的约束,如果在JDK 1.5及以上使用完全没有问题。
- 在Android开发中,在开源框架Android-Universal-Image-Loader的ImageLoader中就是使用《双重校验锁》的方式,所以在Android开发中,我们可以直接使用《双重校验锁》的方式。
- Android开发中,单例模式常会用于我们的对业务的封装类中,如:对网络请求的封装、图片加载的封装等。
单例模式使用的一个实际例子见下一篇。