概要
好无聊,无聊到只能用学习来打发时间了。
刚刚听音乐,正好放到了一首我非常喜欢的歌 一生所爱。就是大话西游里面的那首。每每听到这首歌,心,痛。
咦,好像一生所爱和我今天要总结的单例模式关联不上了,不管了,下面直接开始总结,不然我的心就白白痛了啵。
饿汉式
个人以为饿汉式是实现单例最简单粗暴等方式了。无论是谁,随便拿一本 Java 书籍,只要看过前 30 页,就能写饿汉式的单例。原因很简单只要知道静态变量在类加载的时候就被创建了,那么任意定义静态变量并直接初始化,你不想单例都难了。
class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {}
public static SingletonHungry getInstance() {
return instance;
}
}
饿汉式的单例实现起来十分简单而且还是线程安全的,但是并没有延时初始化,这会对内存造成浪费,就像很多安卓手机应用,明明没用到,却在开机时候就在后台偷偷启动了。不过鉴于如今硬件升级速度之快,可以让我们不用那么关心这点内存问题。
懒汉式
懒汉式分两种,一种是线程不安全的,还有一种是线程安全的。
如下这段个单例在多线程环境下就有可能会出错。
//懒汉式 线程不安全
class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if (null == instance) {
instance = new SingletonLazy();
}
return instance;
}
}
多线程出错演示
接下来是线程安全的懒汉式
//懒汉式 线程安全
class SingletonLazySafe {
private static SingletonLazySafe instance;
private SingletonLazySafe() {}
public static synchronized SingletonLazySafe getInstance() {
if (null == instance) {
instance = new SingletonLazySafe();
}
return instance;
}
}
看线程安全和不安全的两种实现方式代码其实没什么差别,唯一不同的地方就是线程安全的方式加了一个关键字 synchronized,用于确保 getInstance 方法线程安全。但是这样线程安全是安全了,那么如果接下来有上百上千个线程来访问这个方法,每个线程调用一次就把这个方法先锁一次,你说你死不死。所以这两种其实都不是实现单例的好方法。
双重校验模式 double-checked locking
说到底 双重校验模式 也是 懒汉式 的一种。它解决了上面懒汉式效率低下的问题。
class SingletonDCL {
private static SingletonDCL instance;
private SingletonDCL() {}
public static SingletonDCL getInstance() {
if (null == instance) {
synchronized (SingletonDCL.class) {
if (null == instance) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
可以从代码中看出,我们学聪明了,没有将整个 getInstance 方法给锁住,而是只锁创建实例的部分。另外进行了两次 null 判断。
这两次判断还是比较讲究的,第一次判断是否为 null 的意图很明显,就是为了如果实例已经存在那么我们就直接返回实例,不要再做任何多余的操作。
第二次判断是为了解决多线程情况下创建多个实例的问题。(假设一下没有这个判断,当两个线程 A,B 同时访问这个方法,A 线程先获取了锁,B 线程在等待。A 线程完成实例化操作,释放了锁,然后 B 线程开始执行实例化操作,这个时候因为没有判断 instance 已经被实例化过了,又重新实例化一遍,这就不是单例了。)
所以,双重校验模式才是真正的懒汉式,嗯!
登记式/静态内部类
使用静态内部类方式能达到双重校验模式一样的效果,而且实现更加简单。
这里用到了 Java 静态内部类的特性,加载类的时候,不会加载静态内部类,只有在使用的时候才会进行显示装载。
class SingletonRegister {
private static class SingletonInner {
private static final SingletonRegister INSTANCE = new SingletonRegister();
}
private SingletonRegister() {
public static SingletonRegister getInstance() {
return SingletonInner.INSTANCE;
}
}
枚举方式
说实话,从没有在实际项目中看到用枚举方式实现单例的,不过这是 Effective Java 作者 Josh Bloch 提倡的方式。
这种方式实现起来简单的让人疑惑 这是单例吗?没错,还真是。
enum SingletonEnum {
INSTANCE("url", "name", "pwd", "etc");
private String url;
private String name;
private String pwd;
private String etc;
SingletonEnum(String url, String name, String pwd, String etc) {
this.url = url;
this.name = name;
this.pwd = pwd;
this.etc = etc;
}
public void doSomething() {
System.out.println("do something");
}
public SingletonEnum getInstance() {
return INSTANCE;
}
}
## 总结
一般在实际项目中使用到单例模式时候,会以饿汉模式作为首选,如果有延时加载要求的那才会用内部类实现方式。如果有反序列化需求的,那么可以用枚举方式实现,毕竟 Enum 已经实现了 Serializable 接口。
总结好了,老朽要再去刷遍《大话西游》了。