作用:
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是非常可观的一笔系统开销。由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。
所以对于系统的关键组件和被频繁操作的对象,使用单例模式便可以有效地改善系统性能。
下面来说说单例模式的几种写法
(1)饿汉式单例模式
/**
* 饿汉式(Java)
*/
public class SingletonDemo {
private static SingletonDemo singletonDemo = new SingletonDemo();
private SingletonDemo(){}
public static SingletonDemo getInstance(){
return singletonDemo;
}
}
/**
* 饿汉式(Kotlin)
*/
object SingletonDemo {
val instance: SingletonDemo = SingletonDemo
}
以上是饿汉式单例的标准写法,分析结果如下:
- 由于SingletonDemo实例的创建时静态的,所以当SingletonDemo加载的时候就会创建实例;
- 当SingletonDemo中的getInstance方法被执行的时候,SingletonDemo将被加载,从而new了一个SingletonDemo实例;
- 但是当SingletonDemo类中有其他成员的时候,假如多了一个A方法,首先执行A方法后,SingletonDemo实例也会被创建,也就是说,在不需要SingletonDemo对象的时候依然创建了SingletonDemo实例,造成资源不必要的浪费,那么有没有办法做到当使用的时候再创建实例呢?
想要避免以上弊端就不能让SingletonDemo静态加载,我们改写代码:
/**
* 懒汉式(java)
*/
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance == null){
instance = new SingletonDemo();
}
return instance;
}
}
/**
* 懒汉式(Kotlin)
*/
object SingletonDemo {
var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo
}
return field
}
private set
}
采用延迟加载(懒汉式)的方式解决以上弊端,但是这样解决方式并不是完美的,我们不仅要考虑实例单一,还要考虑线程安全,很显然以上代码会带来线程安全危机,当多个线程同时调用getInstance方法时,有可能会new多个实例,接下来我们来解决线程安全问题。
(2)懒汉式单例模式
/**
* 线程安全的懒汉式(Java)
*/
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static synchronized SingletonDemo getInstance(){
if(instance == null){
instance = new SingletonDemo();
}
return instance;
}
}
/**
* 线程安全的懒汉式(Kotlin)
*/
object SingletonDemo {
@get:Synchronized
var instance: SingletonDemo? = null
get() {
if (field == null) {
field = SingletonDemo
}
return field
}
private set
}
以上是线程安全的懒汉式的标准写法。
添加synchronized锁,使在同一时间内,只能有一个线程执行getInstance方法,这样虽然决绝了线程安全的问题,但是效率严重降低,那么怎么才能提高效率呢?
(3)双检查锁机制(DCL:double checked locking)
/**
* 双重校验锁(Java)
*/
public class SingletonDemo {
private static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance == null){
synchronized (SingletonDemo.class){
if(instance == null){
instance = new SingletonDemo();
}
}
}
return instance;
}
}
/**
* 双重校验锁(Kotlin)
*/
object SingletonDemo {
var instance: SingletonDemo? = null
get() {
if (instance == null) {
synchronized(SingletonDemo::class.java) {
if (instance == null) {
instance = SingletonDemo
}
}
}
return instance
}
private set
}
这里有两处判空的地方,如果第一次判断的时候不为空,则跳过synchronized,使效率提高。
这个方案当时被认为是单例模式的最佳方案,但是由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况,jdk1.6之后,只要定义为private volatile static SingleTon instance 就可解决DCL失效问题, volatile确保instance每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅,volatile可以保证即使java虚拟机对代码执行了指令重排序,也会保证它的正确性。(volatile:防止编译器对代码进行优化)
/**
* 双重校验锁(Java)
*/
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instance == null){
synchronized (SingletonDemo.class){
if(instance == null){
instance = new SingletonDemo();
}
}
}
return instance;
}
}
/**
* 双重校验锁(Kotlin)
*/
object SingletonDemo {
@Volatile
var instance: SingletonDemo? = null
get() {
if (instance == null) {
synchronized(SingletonDemo::class.java) {
if (instance == null) {
instance = SingletonDemo
}
}
}
return field
}
private set
}
以上代码才是DCL的标准写法,一定要添加volatile修饰才能保证绝对安全性。
所谓 指令重排 的概念可以举例说明:
创建一个对象需要3条指令:
指令1:开辟内存
指令2:对象初始化(内存填值)
指令3:将指针指向该内存
volatile 是一个指令关键字。
如果不添加 volatile ,那么在多线程的情况下,99.99%的概率是线程安全的,但是有0.01%的概率是不安全的。
如果一旦触发这个0.01%的概率,发生了`指令重排`,那么创建对象的指令可能变成:
指令1:开辟内存
指令3:将指针指向该内存 ---- 此时对象已经不为空了
指令2:对象初始化(内存填值)
这样会导致对象没有初始化完成。
(4)静态内部类单例模式
/**
* 静态内部类(Java)
*/
public class SingletonDemo {
private SingletonDemo(){}
static class SingleHolder{
public static SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo getInstance(){
return SingleHolder.instance;
}
}
/**
* 静态内部类(Kotlin)
*/
object SingletonDemo {
val instance: SingletonDemo
get() = SingleHolder.instance
internal object SingleHolder {
var instance: SingletonDemo = SingletonDemo
}
}
或者
/**
* 静态内部类(Kotlin)
*/
class SingletonDemo {
companion object {
val instance: CrashHandler
get() = CrashHandlerHolder.instance
internal object CrashHandlerHolder {
var instance: CrashHandler = CrashHandler()
}
}
}
分析一下这种写法:
(1)当调用SingletonDemo类中的其他成员时,SingletonDemo加载之后并没有创建实例,所以解决了饿汉式的弊端;(只有SingleHolder内部类被加载的时候才会创建SingletonDemo实例)
(2)由于静态的特性,没有线程安全问题,所以解决了懒汉式的弊端;
(3)由于反射机制无法侵入静态内部类,从而加深了安全性;
(5)用枚举实现单例模式
其实,前四种设计模式存在一个共同的弊端,那就是一旦序列化之后就不再单例,具体原因可以查询其他资料。
public class SingletonDemo {
private String A;
private String B;
public enum MyEnumSingleton {
INSTANCE;
private SingletonDemo singletonDemo;
private MyEnumSingleton(){
singletonDemo = new SingletonDemo();
}
public SingletonDemo getInstance(){
return singletonDemo;
}
}
public static SingletonDemo getInstance(){
return MyEnumSingleton.INSTANCE.getInstance();
}
}
以上代码就是枚举单例了。
SingletonDemo singletonDemo1 = SingletonDemo.getInstance();
System.out.println(String.valueOf(singletonDemo1.hashCode()));
SingletonDemo singletonDemo2 = SingletonDemo.getInstance();
System.out.println(String.valueOf(singletonDemo2.hashCode()));
SingletonDemo singletonDemo3 = SingletonDemo.getInstance();
System.out.println(String.valueOf(singletonDemo3.hashCode()));
打印hashcode发现是一致的,说明singletonDemo1 、singletonDemo2 、singletonDemo3 是同一个对象。
枚举单例的优点:
- 线程安全
- 代码简洁
- 序列化之后依然是单例
- 反射机制无法侵入
枚举单例的缺点:
- 消耗的内存大概是静态内部类单例的两倍