单例模式(DCL、holder等)

单利模式

单利模式简单的来说由两部分组成:

  1. 只实例化一次
  2. 提供返回这个实例的方法

单利模式的好坏在于线程安全性能(效率)懒加载这三个属性的好坏

那怎么实现单利模式呢?也就是怎么才能保证一直只有一个实例呢?
很快我们就能想到static关键字

饿汉模式

public class HungerySingletonDemo {
	private static HungerySingletonDemo instance=new HungerySingletonDemo();
	
	private HungerySingletonDemo() {}
	
	public static HungerySingletonDemo getInstance() {
		return instance;
	}
}

线程安全、性能好,但是不能实现懒加载,当HungerySingletonDemo占用变大时会非常占用内存(一开始不使用这个对象却要占用大量内存)

懒汉模式

先要实现懒加载,就在第一次调用的时候实例它好了

public class HoonSingleton {
    private static HoonSingleton instance=null;
	
	private HoonSingleton() {}
	
	public static  HoonSingleton getInstance() {
		if(instance==null) {
			instance = new HoonSingleton();
		}
		return instance;
	}
}

实现了懒加载,但是线程不安全,因为如果当多个线程同时访问到if(instance==null) 这行代码,都会执行到 if 内部,这样就会创建多个实例。线程都不安全就没有性能好坏这个说法了。
那有没有这么方法来改进呢?也就是怎么控制线程的并发访问呢?
很明显我们会想到synchronized关键字。

public static synchronized HoonSingleton getInstance()

这样的话线程就可同步访问getInstance这个方法,就不会创建这个实例了
但问题又随着而来了,synchronized效率太低了,性能不好。那怎么才能提高性能呢?这时,我们可能会想到让同步代码执行次数少点不就好了吗,那怎么才能使同步代码执行次数变少呢?请看下例DCL。

DCL(Double Check Lock)

public static  HoonSingleton getInstance() {
	if(instance==null) {
		synchronized(HoonSingleton.class) {
			instance = new HoonSingleton();
		}
	}
	return instance;
}

如上,是不是减少了同步代码的执行次数(instance==null就不会执行同步代码)
但是这样的话有出现了线程安全的问题,因为如果有多个线程都进入到instance==null 里面的 if 语句块,就会在synchronized代码块进行并发操作,无论如何最后都会并发完,结果也会创建多个实例。那如何改进呢?这就是DCL(执行两次判断语句)。

public class DCLSingleton {
    private static DCLSingleton instance=null;
	
	private DCLSingleton() {}
	
	public static DCLSingleton getInstance() {
		if(instance==null) {
			synchronized(DCLSingleton.class) {
				if(instance==null) {
					instance = new DCLSingleton();
				}
			}
		}
		return instance;
	}
}

如上,判断了两次instance==null ,这就防止了创建多个实例(synchronized内部再判断一次,如果不为空就不创建实例)。但这个有可能会存在问题,如下:
存在问题
假设线程一执行到instance = new DCLSingleton();这句,这里看起来是一句话,但实际上其被编译后在JVM执行的对应会变代码就发现,这句话被编译成8条汇编指令,大致做了三件事情:

1)给instance实例分配内存;

2)初始化instance的构造器;

3)将instance对象指向分配的内存空间(注意到这步时instance就非null了)

如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。如此,在程序真正运行时以上指令执行顺序可能是这样的:

a)给instance实例分配内存;

b)将instance对象指向分配的内存空间;

c)初始化instance的构造器;

这时候,当线程一执行b)完毕,在执行c)之前,被切换到线程二上,这时候instance判断为非空,此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。

具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)。

根据以上分析可知,解决这个问题的方法是:禁止指令重排序优化(相对的),即使用volatile变量。

 private static volatile  DCLSingleton instance=null;//在该属性上加volatile

Holder模式(使用广泛)

声明类时不声明实例变量,而放到一个内部静态类中去实例化该类

public class HolderSingleton {
	private static class Holder{
		private static HolderSingleton instance=new HolderSingleton();
	}
	
	public static HolderSingleton getInstance() {
		return HolderSingleton.Holder.instance;
	}
}

加载HolderSingleton时,也会加载内部类Holder,但它不会初始化,当首次调用getInstance时才初始化化(就是实现了懒加载)。

枚举模式(使用广泛)

Holder模式的内部类替换成enum类型的,因为枚举类型的成员时(INSTANCE)也是public static final的,且是在static{}中初始化的(就是实现了懒加载)。

public class EnumSingleton {
	private EnumSingleton() {}
	
	private enum EnumHolder{
		INSTANCE;//public static final EnumHolder实例,在static块初始化
		private EnumSingleton instance=null;//EnumHolder实例的私有变量
		EnumHolder(){
			instance = new EnumSingleton();
		}
	}
	
	public static EnumSingleton getInstance() {
		return EnumHolder.INSTANCE.instance;
	}

}

你可能感兴趣的:(Java,并发)