定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
功能
单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。
饿汉法
顾名思义,饿汉法就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。代码如下:
public class Singleton {
private static Singleton = new Singleton();
private Singleton() {}
public static getSignleton(){
return singleton;
}
}
饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。但是无法做到延迟创建对象,从而减小负载。
懒汉式
单线程写法
public class Singleton {
private static Singleton singleton = null;
private Singleton(){}
public static Singleton getSingleton() {
if(singleton == null) singleton = new Singleton();
return singleton;
}
}
这种不加同步的懒汉式写法虽然简单但同时它是线程不安全的,这种写法由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,如果是null就new一个出来,最后返回singleton对象。这种方法可以实现延时加载,但是有一个致命弱点:线程不安全。
线程安全的写法
如何实现懒汉式的线程安全?加上synchronized?
public static synchronized Singleton getInstance(){}
但这样会降低整个访问的速度,而且每次都要判断。现在比较流行的是用双重检查加锁。
双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。
双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
/**
* 双重检查加锁的单例模式
* @author dream
*
*/
public class Singleton {
/**
* 对保存实例的变量添加volitile的修饰
*/
private volatile static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查实例是否存在,如果不存在才真正的创建实例
instance = new Singleton();
}
}
return instance;
}
}
但是双重加锁机制,一句话说是“成也volatile,败也volatile”,因为volatile关键字在JDK1.5之前无法保证线程安全,所有双重加锁机制也不是完美的,也是有瑕疵的。
一种更好的单例实现方式
静态内部类法,它可以延时加载,并且能保证线程安全,我们把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。
public class Singleton {
/**
* The class-level inner class, that is, the member-like inner class of the static class, the instance of
* the inner class has no binding relationship with the instance of the outer class, and it will be loaded
* only when it is called, thus realizing lazy loading .
* @author dream
*/
private static class SingletonHolder{
/**
* Static initializer, thread safety is guaranteed by JVM.
*/
private static Singleton instance = new Singleton();
}
/**
* Privatization construction method.
*/
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
优缺点
- 懒汉式是典型的时间换空间
- 饿汉式是典型的空间换时间
何时选用单例模式
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是jdk版本)下,自然有不同的最优解(或者说较优解),比如双重检查锁法,不能在jdk1.5之前使用,但是本人是Android开发者,而Android基本都是基于JDK1.6以上开发的,所以我个人偏爱比较双重检查锁法。
当然,还有一种更加优雅的写法:枚举写法,但为什么我没有介绍呢,应为它在Android平台上却是不被推荐的。所以感兴趣的同学可以自行去了解一下。