单例模式:DCL+volatile 失效!

大名鼎鼎的DCL+volatile 保证单例模式的组合居然失效了!
public class Singleton_03 {
private static volatile Singleton_03 instance_04;//由于new 一个对象分为三个步骤 不是原子性的操作 1-分配内存空间 2-初始化对象 3-指向对内存地址   2和3步骤可以指令重排
public Singleton_03 getInstance_04() {
    if (instance_04 == null) {
       try {
            Thread.sleep(1L);
        } catch (InterruptedException e) {
          e.printStackTrace();
       }
        synchronized (Singleton_03.class) {
            if (instance_04 == null) {
               try {
                  Thread.sleep(1L);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
                instance_04 = new Singleton_03();
            }
        }
    }
    return instance_04;
}
private static Set<Singleton_03> singleton_03Set_04 = CollUtil.newHashSet();
//---------------------------instance_04加了volatile
//getInstance_04()----> 实例数量:5
@Test
public void test() {
    test_04();
}
@Test
public void test_04() {
    IntStream.range(1, 1000).forEach(i -> new Thread(() -> singleton_03Set_04.add(getInstance_04())).start());
    try {
        Thread.sleep(2000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (singleton_03Set_04.size() > 1) {
        System.out.println("getInstance_04()----> 实例数量:" + singleton_03Set_04.size());
    }
}

}

运行结果

getInstance_04()----> 实例数量:2

Process finished with exit code 0

我的解答思路和尝试过的方法
我想要达到的结果

单例的实现
1.单线程下的Lazy实现
public class Main {
private static Main instance = null;

private Main() {}

public static Main getInstance() {
    if(null == instance) instance = new Main();
    return instance;
}

}
2.针对1的多线程阻塞实现
就是改进了check-then-act的原子性问题

public class Main {
private static Main instance = null;

private Main() {}

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

}
3.错误的双重加锁
public class Main {
private static Main instance = null;

private Main() {}

public static Main getInstance() {
    if(null == instance) synchronized(Main.class) {
        if(null == instance) instance = new Main(); // 问题出现在初始化
    }
    return instance;
}

}
注意可见性是正确的,错误在于初始化的重排序

上一篇文章已经写了3个步骤,一个线程在判断第一个if的时候可能另一个线程执行到第二个步骤就写入引用了,这时返回的是默认值

4.正确的双重加锁
既然重排序有问题那当然要volatile

public class Main {
private static volatile Main instance = null;

private Main() {}

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

}
5.静态内部类
利用class文件对于内部类的特性,实现上够简单

public class Main {

private static class InstanceHolder {
    final static Main INSTANCE = new Main();
}

public static Main getInstance() {
    return InstanceHolder.INSTANCE;
}

}
6.枚举类
仅访问Singleton本身不会使Singleton.INSTANCE初始化

public enum Singleton {
INSTANCE;
Singleton() {}
public void doSomething() {}
}

public class Main {
public static void main() {
new Thread() {
public void run() {
Singleton.INSTANCE.doSomething();
}
}.start();
}

}
懒汉?饿汉?
补充一下奇怪的术语:懒汉式、饿汉式

其中懒汉式就是带Lazy加载的意思,比如1、2

而饿汉式我并不太清楚字面上的意思。。应该是指内存宽裕吧。。就是static直接返回的那种,显然不如静态内部类

public class Main {
private static Main instance = new Main();
private Main() {}
public static Main getInstance() { return instance; }
}
破坏单例模式
1.除了enum和静态内部类,其它都可以被newInstance()拿到手
改进方法就是在构造方法创建保护null的判断

2.还有一种反序列化的破坏方式(如果你的单例需要序列化)。。
解决方法是重写readResolve()方法,使得它在方法内直接返回instance

这里提醒一点,如果是jdk1.4或者更早的版本,双检锁是无效的。
其实如果只是想得到单例,使用静态变量初始化的方式就可以了,复杂一些就使用静态内部类。虚拟机会帮助你解决很多问题。
这个在《head first 设计模式》 一书中有提到。

你的命名纯属折磨人;其实DCL没有问题,问题出在Set上。
HashSet底层是HashMap在多线程环境下HashMap线程不安全;size记录元素个数,在HashMap中直接size++,这并不是一个线程安全的计数方法;你可以打印set的元素,虽然size> 1 ,但是set里的元素只有一个;