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里的元素只有一个;