java并发编程第八章ValueLatch在读取的时候是否有必要使用内置锁synchronized?
这里ValueLatch是一个线程安全的类,但是据我个人理解在getValue函数中没有必要使用synchronized(this)同步读取value。
因为以下4个语句
value = newValue; //1
done.countDown(); //2
done.await(); //3
return value; //4
根据happens-before中的程序顺序规则,1和2之前执行,3在4之前执行。
根据CountDownLatch的happens-before原则,2在3之前执行。
根据传递性规则,1在4之前执行。所以已经保证了读操作的可见性,在getValue方法内就没有必要再用内置锁synchronized了。望各位大侠指正下,我的想法是否正确?
-
总之Synchronized可以保证可见性和原子性(Synchronization and the Java Memory Model)(http://gee.cs.oswego.edu/dl/cpj/jmm.html)可见性一节在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存一在主内存中拷贝最新变量的副本到工作内存一执行完代码一将更改后的共享变量的值刷新到主内存中一释放互斥锁
分析题主回答和题目的区别题目: 1.read操作会从排序? await和Sync入口重拍序吗?根据JSR-133资料The JSR-133 CookbookTheJSR-133 Cookbook(http://gee.cs.oswego.edu/dl/jmm/cookbook.html)。可以看到(1,3)这个坐标,是没有内存屏障的,可以重排序,所以await()这个调用时可以和sync(this)重拍的,然后看(3,1)是有屏障的所以sync方法块里面的不可以排序到sync方法块前面去。总的来说就是sync方法前面的代码是可以重排序的,但是一定会happens-before方法块里面的代码。
通过下面这个表格对比可以分清楚是如何加屏障的
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。StoreLoad屏障:对于这样的语句Store1;StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障
那么总的来说对于题目而言,3 hapepens-before 4,1和2也happends-before 4所以最后读取value是肯定线程安全的。同时根据sync有可见性,可以及时保障value是正确的值
2.针对第二种情况,(hp=happens-before)
明显的作为就是利用volatile的可见性和内存语义来保证的线程安全。volatile写之前会有storestore的内存屏障,JMM会把该线程对应的本地内存中的共享变量刷新到主内存所以value也是线程安全的。和第一种有区别,但是都差不多。有些会说这种方式效率更高,其实也不一定sync现在已经非常优化了
-
以下引自《深入理解java内存模型-程晓明著》,看到这跟问题中的情况很像。
请看下面使用 volatile 变量的示例代码:
class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } public void reader() { if (flag) { //3 int i = a; //4 ...... } } }
假设线程 A 执行 writer()方法之后,线程 B 执行 reader()方法。根据 happens before 规则,这个过程建立的 happens before 关系可以分为两类:
- 根据程序次序规则,1 happens before 2; 3 happens before 4。
- 根据 volatile 规则,2 happens before 3。
- 根据 happens before 的传递性规则,1 happens before 4。
上述 happens before 关系的图形化表现形式如下:
-
我也觉得是这样。除非有CountDownLatch的实现不保证可见性,不过想了下好像不可能。
以1.7的CountDownLatch实现来说,它继承自AbstractQueuedSynchronizer。当线程1先执行done.await()时,观察到AQS的state是1,那么它阻塞自己。随后线程2执行done.countDown(),CAS操作state减1变成0。那么线程1被唤醒,去读线程2CAS改变state后的值,是0,于是继续执行。由于线程2先执行CAS操作,然后线程1去读,那么线程2之前写入的值,线程1是可以看到的。
发表回复