作为程序员这样的特殊物种来说,都掌握了一种特殊能力就是编程思想,逻辑比較慎重,可是有时候总会忽略到一些细节,比方我,一直以来总认为Singleton
是设计模式里最简单的,不用太在意,然而就是由于这样的不在意在开发中吃亏了.真的too young to simple.
好不扯淡了,直入主题.
在代码的世界里发现有各种写法的单例,有人说单例有5种,6种,7种…
对于单例的分类这点必须规范下,首先这么多种的分类是依据什么来定义的,基准是什么?
否则怎么会有那么多写法.
因此归纳下来,从延迟载入
和运行效率
的角度出发主要也就分为两种,饿汉顾名思义就是运行效率高,但缺乏延时载入,其它写法差点儿相同都是懒汉式的一个拓展,或者优化而演化出来的,以下请看代码.
开发中经常使用的单例-饿汉式
public class SingletonDemo1 {
private static final SingletonDemo1 s1 = new SingletonDemo1();
public static SingletonDemo1 getInstance() {
return s1;
}
private SingletonDemo1() {
}
}
没错上面这块代码叫做单例-饿汉式
,饿汉式一直以效率高而闻名于单例界,因此咋们开发中经常使用的单例模式也会选择他,简单而好用.
>
开发评价: ★★★★☆
延时载入: ★☆☆☆☆
运行效率: ★★★★★
耗时的蜗牛-懒汉式
public class SingletonDemo2 {
private static SingletonDemo2 s1;
public static synchronized SingletonDemo2 getInstance() {
if (s1 == null) {
s1 = new SingletonDemo2();
}
return s1;
}
private SingletonDemo2() {
}
}
在hello world
这个世界里都知道这样的单例基本不会去用,在
double check双重检查锁-懒汉式
这能够说是上面饿汉式的一个缩影,为什么这么说,由于他并不完美,仍然有bug.
public class SingletonDemo3 {
private static SingletonDemo3 s1;
public static SingletonDemo3 getInstance() {
if (s1 == null) {
//这里使用了暂时变量
SingletonDemo3 instance;
synchronized (SingletonDemo3.class) {
instance = s1;
if (instance == null) {
synchronized (SingletonDemo3.class) {
if (instance == null) {
instance = new SingletonDemo3();
}
}
s1 = instance;
}
}
}
return s1;
}
}
这个方式主要是通过if推断非null实例,提高了运行的效率,不必每次getInstace
都要进行synchronize,仅仅要第一次要同步,有没创建了不用.
可是为什么说这样的写法有bug?这个问题主要是java的jvm层内部模型引起的.简单点说就是instance引用的对象有可能还没有完毕初始化完就直接返回该实例过去
,在jdk1.5后这个问题才得到了优化,这不多说,能够看看这篇博文讲得不错.
详情见
当然也有了一些解决方法
- 使用volatile关键字解决双重检查锁定的bug,对于volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案.
public class SafeDoubleCheckedLocking {
//加入了volatile关键字
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized (SafeDoubleCheckedLocking.class) {
if (instance == null)
instance = new Instance();//instance为volatile。如今没问题了
}
}
return instance;
}
}
>
开发评价: ★★★☆☆
延时载入: ★★★☆☆
运行效率: ★★★☆☆
推荐使用的静态内部类-懒汉式
public class SingletonDemo4 {
//通过静态内部类的方式来实例化对象
private static class InnerSingleton {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
public static SingletonDemo4 getInstance() {
return InnerSingleton.instance;
}
private SingletonDemo4() {
}
}
这周方式是利用了类载入的一些特性,在classloder的机制中,初始化instance时仅仅有一个线程,并且这样的方式另一个优点就是起到了延时载入的效果,当SingletonDemo4
被载入了,可是内部类InnerSingleton
并不会被载入,由于InnerSingleton
没有主动使用,仅仅有通过调用getInstance
方法时才会去载入InnerSingleton
类,进而实例private static final SingletonDemo4 instance = new SingletonDemo4();
因此这样的巧妙的方式比起双重检查锁来说,安全来又高效了些.
>
开发评价: ★★★★☆
延时载入: ★★★★☆
运行效率: ★★★★☆
推荐使用的枚举-饿汉式
public enum SingletonDemo5 {
//枚举元素本身就是一个单例(名字能够任意定义);
INSTANCE;
//能够做一些单例的初始化操作
public void singletonOperation() {
}
}
这样的方式事实上非常帅,可是在实际开发中非常少人使用过,这点有点奇怪,首先enum本身就是一个单例,并且枚举另一个特性,能够避免放序列化和反射破解单例问题,经理再也不用操心单例安全了,可能是1.5才有enum的原因吧,假设项目适合的话能够试下这样的单例.
>
开发评价: ★★★★☆
延时载入: ★★★★☆
运行效率: ★★★★☆
总结一下:
对于以下的单例总的来说各有各的优点,至于开发中使用哪个能够依据你的业务需求来选择.
- 饿汉
- 标准饿汉 (安全防护方面 枚举单例更优于标准饿汉)
线程安全,高效,不能够懒载入 - 枚举单例
线程安全,高效,不能够懒载入(天然避免反射与反序列化)
- 标准饿汉 (安全防护方面 枚举单例更优于标准饿汉)
- 懒汉 (效率方面 静态内部类更优于标准懒汉)
- 标准懒汉
线程安全,低效,能够懒载入 - 双重检測(不推荐,有bug)
线程安全,低效,能够懒载入 - 静态内部类
线程安全,低效,能够懒载入
- 标准懒汉
对于单例的安全性问题,能够继续你那帅气的阅读姿势,java中你的单例是不是一直在裸奔