一个说法不去验证就欣然接受,然后再把它传播给别人,荒谬又可悲!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit;
public class DoubleIfSingleton { private static DoubleIfSingleton ins = null;
public static void main(String[] args) throws InterruptedException { int max = 64; CountDownLatch d = new CountDownLatch(1); class R implements Runnable { private CountDownLatch d;
public R(CountDownLatch d) { this.d = d; }
public void run() { try { d.await(); DoubleIfSingleton.getIns(); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
Thread[] ts = new Thread[max]; for (int i = 0; i < max; i++) { ts[i] = new Thread(new R(d)); ts[i].start(); }
d.countDown(); for (int i = 0; i < max; i++) ts[i].join(); }
public static DoubleIfSingleton getIns() { if (null == ins) { synchronized (DoubleIfSingleton.class) { if (null == ins) { ins = new DoubleIfSingleton(); } } } return ins; }
private DoubleIfSingleton() { System.out.println(this.hashCode()); } }
|
通过javap命令查看带volatile和不带volatile的class,运行命令竟然是一样的!
我怀疑是jdk在编译时做了优化。如果换成int呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit;
public class IncreaseT { public static int t = 0;
public static synchronized void add() { for (int j = 0; j < 10000; j++) t++; }
public static void main(String[] args) throws InterruptedException { int max = 128; CountDownLatch d = new CountDownLatch(1); class R implements Runnable { private CountDownLatch d;
public R(CountDownLatch d) { this.d = d; }
public void run() { try { d.await(); IncreaseT.add(); TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }
Thread[] ts = new Thread[max]; for (int i = 0; i < max; i++) { ts[i] = new Thread(new R(d)); ts[i].start(); }
d.countDown(); for (int i = 0; i < max; i++) ts[i].join(); System.out.println(IncreaseT.t); } }
|
javap对比两者的命令不同,volatile没有被编译优化忽略。
所有博客和书都说这种单例模式必须加volatile,然后把java内存模型和happens-before原则拿来扯一扯。唉,可能之前的synchronized和jdk版本的问题,使得这种说法成立,但是现在来看加与不加并没有任何区别,如果我错了请邮件(zxfspace@163.com)通知我,感激不尽。
2017.12.29,今天真是个值得高兴的日子,这个困惑终于解决了。读了单例模式这篇文章,明白了原来自己一直忽略了对象产生的步骤和volatile的禁止指令重排作用,字节码好像是去掉了,但是终究不能完全相信字节码,因为它跟cpu真正执行的情况还是有区别的。
一个class的实例被new出来,过程包括三个部分:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
volatile就是禁止这3个指令重排,那么多线程情况下就不会出现B线程得到一个没有初始化的实例。