CopyOnWriteArrayList类set方法疑惑?
在淘宝内网有位同事提了一个很好的问题,大家能否帮忙解答下?
在CopyOnWriteArrayList类的set方法中有一段setArray(elements)代码,实际上这段代码并未对elements做任何改动,实现的volatile语意并不对CopyOnWriteArrayList实例产生任何影响,为什么还是要保留这行语句?见以下代码红体部分:
[code lang=”java”]
/** The array, accessed only via getArray/setArray. */
private volatile transient Object[] array;
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}
[/code]
这个问题在concurrency-interest邮件列表里也有人讨论:
http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006886.html
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: CopyOnWriteArrayList类set方法疑惑?
我想我可能找到这个问题的答案了,虽然在set方法的的API文档中没有描述它包含的内存语义。但在jdk有个地方描述到了:http://docs.oracle.com/javase/7/docs/api/ 的最下方的几条,摘录过来如下:
中文版API中是这样描述的:
CopyOnWriteArrayList作为java.util.concurrent包中的类之一,当然它也要遵守这个约定。所以才在else里面加了一个 setArray(elements);来保证hb关系。
链接贴错了:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html
这个hb意义何在?如下例子:a为非volatile的某基本类型变量,coal为CopyOnWriteArrayList对象
t1:
x:a = calValue;
y:coal.set….
———————
t2:
m:coal.get…
n:int tmp = a;
假设存在以上场景,如果能保证只会存在这样的轨迹:x,y,m,n.根据上述java API文档中的约定有hb(y,m),根据线程内的操作相关规定有hb(x,y),hb(m,n),根据hb的传递性读写a变量就有hb(x,n),所以t1对a的写操作对t2中a的读操作可见。如果CopyOnWriteArrayList的set的else里没有setArray(elements)的话,hb(y,m)就不再有了,上述的可见性也就无法保证。
的确是这样,实际上不是为了保证COW List本身的可见性,而是保证外部的非volatile变量的HB。
参考:
http://stackoverflow.com/questions/28772539/why-setarray-method-call-required-in-copyonwritearraylist
目的就是让另外一个线程在get时能马上读到更新后的值。作者在代码上加了一句解释,Not quite a no-op; ensures volatile write semantics,意思是说这行不完全是一个空操作,而是用来确保volatile的写语义。volatile的写语义之一就是插入一个lock前缀的指令,让volatile所修饰字段的缓存行(cache line)无效。一旦cache line无效,getArray时,必须先重新填充cache line,这样处理器就不会使用当前处理器缓存行里的旧值。
没有明白 ,数据本来就没有变化 ,cache 是否失效 没有关系呀
是的,这里只是用来保证volatile的语义。
没有明白, 数据本来就没有变化,cache 是否失效 没有关系呀
感觉就是为了符合java规范吧,及时数据没有变化
应该为了能够及时读取到最新修改
假如两个两个线程同时获取 E oldValue = get(elements, index);
第一个线程率先修改了elements;第二发现是相同的没有修改,结果第二次set的时候把修改了的又一次set了一遍
是为了维护happens-before规则,具体可以参考下这个帖子:
http://stackoverflow.com/questions/28772539/why-setarray-method-call-required-in-copyonwritearraylist
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
如注释说明,是为了确保维持 volatile 的写语义,依据JSR-133的说明,4种读写内存屏障保障可见与禁止指令重排,可理解为,
1、在外部调用者调用 set(int index, E element) 方法时,如果被写入元素无变化,也保持一次对 volatie 修饰的 array 写操作,让JVM执行编译的指令时,外部调用者在 调用 set(int index, E element) 所操作的 Object[] array ,始终能通知到其他核心的本地缓存中 array 过期,重新从主存和L3中同步读取,保证读的可见;
2、同时,也会因这个写操作有 Lock 前辍,使得 set(idx, elm) 前后代码行不会发生 cpu 指令重排,保证了前后读写操的各种内存屏障下,使编译执行的指令逻辑顺序与代码逻辑期望一致。