7、并发编程中单例模式的解决方案
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
一、饿汉式
public class HungerySingleton {
//加载的时候就创建对象
private static HungerySingleton instance= new HungerySingleton();
private HungerySingleton(){
}
//返回实例对象
public static HungerySingleton getInstance(){
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
System.out.println(HungerySingleton.getInstance());
}).start();
}
}
}1.1、线程安全性
1.2、缺点
二、懒汉式
public class HoonSynSingletonDemo01 {
private static HoonSynSingletonDemo01 instance = null;
private HoonSynSingletonDemo01() {
}
public static HoonSynSingletonDemo01 getInstance() {
//调用getInstance静态方法,非Null才创建。
if (null == instance) {
instance = new HoonSynSingletonDemo01();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
System.out.println(HoonSynSingletonDemo01.getInstance());
}).start();
}
}
}线程安全性
一个A线程进来后,判断了null == instance(true),这时B线程抢到CPU资源,也开始判断了null == instance(true)。
当A线再次抢到CPU资源,就会创建一个HoonSynSingletonDemo01对象,A线程结束。
B线程恢复,也会创建一个HoonSynSingletonDemo01对象。这样就不能保证实例对象的唯一性。
2.1、解决方案1-同步方法
public synchronized static HoonSynSingletonDemo01 getInstance() {
//调用getInstance静态方法,非Null才创建。
if (null == instance) {
instance = new HoonSynSingletonDemo01();
}
return instance;
}2.2、解决方案2-DCL(Double-Check-Locking)
public static HoonSynSingletonDemo01 getInstance() {
//第一次判断
if (null == instance) {
synchronized (HoonSynSingletonDemo01.class){
instance = new HoonSynSingletonDemo01();
}
}
return instance;
}一个A线程进来后,判断了null == instance(true),这时B线程抢到CPU资源,也开始判断了null == instance(true)。
当A线再次抢到CPU资源,进入同步代码块,创建一个HoonSynSingletonDemo01对象,A线程结束。同时释放锁。
B线程恢复,进入同步代码块,也会创建一个HoonSynSingletonDemo01对象。只是加synchronized同步代码块,无法保证实例对象的唯一性。
DCL(Double-Check-Locking)双重检查锁,即可解决这个问题
public static HoonSynSingletonDemo01 getInstance() {
//第一次判断
if (null == instance) {
synchronized (HoonSynSingletonDemo01.class){
//同步代码块中,也要再次判断
if (null == instance){
instance = new HoonSynSingletonDemo01();
}
}
}
return instance;
}一个A线程进来后,判断了null == instance(true),这时B线程抢到CPU资源,也开始判断了null == instance(true)。
当A线再次抢到CPU资源,进入同步代码块,再次判断了null == instance(true),创建一个HoonSynSingletonDemo01对象,A线程结束。同时释放锁。
B线程恢复,进入同步代码块,再次判断了null == instance(false),不会再创建对象。保证了实例对象的唯一性。
2.3、问题思考
public class HoonSynSingletonDemo02 {
private int id;
private static HoonSynSingletonDemo02 instance = null;
private HoonSynSingletonDemo02() {
id = new Random().nextInt(100) + 1; //(1)
}
public static HoonSynSingletonDemo02 getInstance() {
//第一次判断
if (null == instance) { //(2)
synchronized (HoonSynSingletonDemo02.class){ //(3)
//同步代码块中,也要再次判断
if (null == instance){ //(4)
instance = new HoonSynSingletonDemo02(); //(5)
}
}
}
return instance; //(6)
}
public void getId(){
System.out.println("id:" + this.id); //(7)
}
}利用Happen-Before规则分析DCL
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前(如果A happens-before B,那么Java内存模型将向程序员保证——>A操作的结果将对B可见,且A的执行顺序排在B之前)。
两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
语句(5)只会被执行一次,也就是HoonSynSingletonDemo02只会存在一个实例,这是由于它和语句(4)被放在同步代码块中被执行的缘故(如果去掉语句(3)处的同步代码块,那么这个假设便不成立了)。
instance只有两种的值,要么为null(也就是初始值),要么为执行语句(5)时构造的对象引用。
如果getInstance()是初次调用,它会执行语句(5)构造一个HoonSynSingletonDemo02实例并返回,如果getInstance()不是初次调用,如果不能在语句(2)处检测到非空值,那么必定将在语句(4)处检测到instance的非空值,因为语句(4)处于同步块中,对instance的写入。
假设线程1是初次调用getInstance()方法,紧接着线程2也调用了getInstance()方法和getId()方法。
线程2在执行getInstance()方法的语句(2)时,由于对null == instance并没有处于同步块中,因此线程2可能观察到/观察不到线程1在语句(5)时对instance的写入,也就是说instance的值可能为空/非空。
先假设instance的值非空,线程2观察到了线程1对instance的写入。这时线程2就会执行语句(6)直接返回这个instance的值。
然后线程2对这个instance调用getId()方法,该方法是在没有任何同步情况被调用,这时我们无法利用happen-before规则得到线程1的操作和线程2的操作之间的任何有效的happen-before关系。
DCL问题解决
private volatile static HoonSynSingletonDemo02 instance = null;
根据volatile规则,可以得到:线程1的语句(5) ——> 语线程2的句(2)。根据单线程规则,可以得到:线程1的语句(1) ——> 线程1的语句(5)和语线程2的句(2) ——> 语线程2的句(7)。再根据传递规则,可以得到:线程1的语句(1) ——> 线程2的句(7)。这表示线程2能够观察到线程1在语句(1)时对id的写入值,程序能够得到正确的行为。也可以通过final关键字修饰instance,也可以解决这个问题。
2.4、解决方案3-静态内部类
利用classloder的机制来保证初始化instance时只有一个线程。
public class Singleton {
private static class SingletonHolder{
public static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}2.5、解决方案3-Holder模式
声明类的时候,成员变量中不声明实例变量,而放到内部静态类中。这也是广泛的一种单例模式。
public class HolderDemo {
private HolderDemo(){
}
private static class Holder{
private static HolderDemo instance = new HolderDemo();
}
public static HolderDemo getInstance(){
return Holder.instance;
}
}2.6、解决方案3-枚举
public class EnumSingletonDemo {
private EnumSingletonDemo(){
}
private enum EnumHolder{
INSTANCE;
private static EnumSingletonDemo instance = null;
private EnumSingletonDemo getInstance(){
instance = new EnumSingletonDemo();
return instance;
}
}
//懒加载
public static EnumSingletonDemo getInstance(){
return EnumHolder.INSTANCE.instance;
}
}版权声明
非特殊说明,本文由Zender原创或收集发布,欢迎转载。
ZENDER
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。