单例模式
来源:互联网 发布:软件的盈利模式 编辑:程序博客网 时间:2024/06/03 14:32
单例模式大概有五种:懒汉、饿汉、静态内部类、双检锁和枚举。此外还有在这5种方式基础之上的一些变形形式,但都没有什么本质的不同。
先分别写个示例:
1、懒汉
public class Singleton{
public class Singleton{ private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; }}
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。
2、饿汉
public class Singleton{ private static Singleton instance=new Singleton(); private Singleton(){} public static Singleton getInstance(){ return instance; }}
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
3、静态内部类
public class Singleton{ private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } private Singleton(){} public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; }}这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟饿汉方式不同的是(很细微的差别):饿汉方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),而这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种方式相比饿汉方式就显得很合理。
4、双检锁
public class Singleton { private volatile static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}双层校验, 第一次校验不是线程安全的,也就是说可能有多个线程同时得到singleton为null的结果,接下来的同步代码块保证了同一时间只有一个线程进入,而第一个进入的线程会创建对象,等其他线程再进入时对象已创建就不会继续创建。这是一个很巧妙的方式,如果对整个方法同步,所有获取单例的线程都要排队,但实际上只需要对创建过程同步来保证"单例",多个线程不管是否已经有单例可以同时去请求。
5、枚举
public enum Singleton { INSTANCE; public void whateverMethod(){ System.out.println("whateverMethod"); }}
它不仅线程安全,而且还能防止反序列化重新创建新的对象。如果用枚举去实现一个单例,这样的加载有点类似于饿汉模式,并没有起到lazy-loading的作用。
在用enum实现Singleton时我曾介绍过三个特性,自由序列化,线程安全,保证单例。这里我们就要探讨一下why的问题。
首先,我们都知道enum是由class实现的,换言之,enum可以实现很多class的内容,包括可以有member和member function,这也是我们可以用enum作为一个类来实现单例的基础。另外,由于enum是通过继承了Enum类实现的,enum结构不能够作为子类继承其他类,但是可以用来实现接口。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的。
其次,enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。这里举一个例子。
enum Color{ RED(1),GREEN(2),BLUE(3); private int code; Color(int code){ this.code=code; } public int getCode(){ return code; }}
需要注意的是,private修饰符对于构造器是可以省略的,但这不代表构造器的权限是默认权限。
目前我们对enum的结构和特性有了初步的了解,接下来探究一下原理层次的特性。
想要了解enum是如何工作的,就要对其进行反编译。
反编译后就会发现,使用枚举其实和使用静态类内部加载方法原理类似。枚举会被编译成如下形式:
public final class T extends Enum{
...
}
其中,Enum是Java提供给编译器的一个用于继承的类。枚举量的实现其实是public static final T 类型的未初始化变量。如果枚举量有伴随参数并且手动添加了构造器,那么将会解析成一个静态的代码块在类加载时对变量进行初始化。所以,如果用枚举去实现一个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作用。
对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,因此也不存在实现序列化接口后调用readObject会破坏单例的问题。
对于线程安全方面,类似于普通的饿汉模式,通过在第一次调用时的静态初始化创建的对象是线程安全的。
因此,选择枚举作为Singleton的实现方式,相对于其他方式尤其是类似的饿汉模式主要有以下优点:
1. 代码简单
2. 自由序列化
至于lazy-loading,考虑到一般情况不存在调用单例类又不需要实例化单例的情况,所以即便不能做到很好的lazy-loading,也并不是大问题。换言之,除了枚举这种方案,饿汉模式也在单例设计中广泛的被应用。例如,Hibernate默认的单例,获取sessionFactory用的HibernateUtil类建立方式如下:
public class HibernateUtil { private static final SessionFactory ourSessionFactory; static { try { Configuration configuration = new Configuration(); configuration.configure(); ourSessionFactory = configuration.buildSessionFactory(); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); } } public static Session getSession() throws HibernateException { return ourSessionFactory.openSession(); }}
这是一个典型的饿汉模式,考虑到这个单例只有一个方法即getSession,显然这种模式本身就是最优的且简洁的。这里面由于SessionFactory的创建并不是用系统默认的方式,如果想要用enum去实现反而麻烦且无必要。不过至少说明这样做也许需要一个解决自由序列化的问题。
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- 创建 删除文件 文件夹命名
- BAPI_OUTB_DELIVERY_CREATE_SLS 创建外向交货单(参考SO)
- Verilog中assign的用法
- 解决Zookeeper无法启动的问题
- 链表翻转的是三种方法
- 单例模式
- .net 简易数据库操作框架1.6
- Linux多线程──3个子线程轮流运行
- add(int index, E element) 在列表的指定位置插入元素
- day58:hibernate04_JPA注解方式实现hibernate CRUD--【用户添加角色没那摩神秘】
- [HDU] 2054 -A == B ?
- 【HDU4336】Card Collector-状态压缩DP+期望DP
- python绘图实践-泰坦尼克号绘图
- 设计模式系列之「状态模式」