概念
在整个系统中只出现该类的一个对象。基本思路就是私有化其构造函数,在提供一个静态类的成员变量作为私有成员,提供一个类似getInstance()的方法来返回该对象,下面介绍实现单例模式的几种方式:
饿汉式
当类被加载的时候对象就被创造
|
|
这种饿汉式写法的优点就是不会有多线程的问题,确保对象只有一个;缺点就是无论是否使用该对象,都会被创建,不能够延迟加载,也不能够有效减少负荷。
懒汉式
针对上述缺点,提供了懒汉式的写法
|
|
这种写法能够达到延迟加载的效果,有效的减小了系统负荷,但是,如果是多线程的情况下,有可能会生成多个实例,因此不能保证单例,连最基本的需要都达不到怎么行?
synchronized改进1
在Java中,如果需要多线程同步,首先想到的就是synchronized
关键字。那么如果我们加上synchronized
如何?
|
|
上述代码在极端情况下仍会出现多例的情况,例如,两个线程同时进入了if(singleton == null)
的块里,那么一个线程先创建对象,另一个也会再次创建。如果把synchronized (Singleton.class)
写在外层,如:
这样虽然能够达到在多线程下确保单例,但效率非常低下,因为每次得到实例都要进行加锁,解锁的操作。
synchronized改进2
对于上面加synchronized
的情况,似乎都不太好,因此我们考虑使用两次if(singleton == null)
来实现确保单例的同时又保证效率
|
|
上述代码基本能够满足懒汉式创建单例时,保证多线程下能够正常工作。这种方式也可以叫做双重检查“Double-Check”法
到此为止,我们觉得应该大功告成了吧,如果是C++(synchronized
换成加锁,解锁的操作)那么应该就没问题了,但是在Java中,JVM会进行指令重排序,因此,对于一个简单的new
, 如Singleton singleton = new Singleton()
,它并不是原子的,它可以分为三个步骤:
- 分配内存地址
- 初始化成员变量
- singleton引用指向该地址
如果重排序的结果是1,3,2,,在线程执行完1,3后,被另一个线程抢占了,由于3执行后已经非null,线程2可以使用singleton,但实际上它并没有初始化(执行2),因此可能出错。在使用以上写法时,考虑使用关键字volatile
禁止指令重排序.
两种更可靠的写法
静态内部类法
|
|
即保证了效率有可以延迟加载,很好!!
但是!!,以上的方法有两个缺陷:1. 需要额外的工作来保证序列化,反序列化会创建新的对象;2. 可以通过反射机制来调用私有构造函数
枚举写法
终极写法!!《Efftive Java》推荐写法
优点:
- 非常简单
- 默认线程安全
- 防止反射机制调用私有构造函数
- 提供了自动序列化机制,防止反序列化的时候创建新的对象
|
|
或者更优雅
默认枚举实例的创建是线程安全的,但是在枚举中的其他任何方法由程序员自己负责。