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方法疑惑?

  • Trackback 关闭
  • 评论 (13)
    • Ticmy
    • 2013/01/16 12:46下午

    我想我可能找到这个问题的答案了,虽然在set方法的的API文档中没有描述它包含的内存语义。但在jdk有个地方描述到了:http://docs.oracle.com/javase/7/docs/api/ 的最下方的几条,摘录过来如下:

    The methods of all classes in java.util.concurrent and its subpackages extend these guarantees to higher-level synchronization. In particular:Actions in a thread prior to placing an object into any concurrent collection happen-before actions subsequent to the access or removal of that element from the collection in another thread.

    中文版API中是这样描述的:

    java.util.concurrent 中所有类的方法及其子包扩展了这些对更高级别同步的保证。尤其是: 线程中将一个对象放入任何并发 collection 之前的操作 happen-before 从另一线程中的 collection 访问或移除该元素的后续操作。

    CopyOnWriteArrayList作为java.util.concurrent包中的类之一,当然它也要遵守这个约定。所以才在else里面加了一个 setArray(elements);来保证hb关系。

      • Ticmy
      • 2013/01/16 1:01下午
        • Ticmy
        • 2013/01/16 1:25下午

        这个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)就不再有了,上述的可见性也就无法保证。

      • michaelchan
      • 2016/02/18 10:52上午

      的确是这样,实际上不是为了保证COW List本身的可见性,而是保证外部的非volatile变量的HB。

      参考:
      http://stackoverflow.com/questions/28772539/why-setarray-method-call-required-in-copyonwritearraylist

  1. 目的就是让另外一个线程在get时能马上读到更新后的值。作者在代码上加了一句解释,Not quite a no-op; ensures volatile write semantics,意思是说这行不完全是一个空操作,而是用来确保volatile的写语义。volatile的写语义之一就是插入一个lock前缀的指令,让volatile所修饰字段的缓存行(cache line)无效。一旦cache line无效,getArray时,必须先重新填充cache line,这样处理器就不会使用当前处理器缓存行里的旧值。

      • dys
      • 2013/01/22 9:22上午

      没有明白 ,数据本来就没有变化 ,cache 是否失效 没有关系呀

      • 是的,这里只是用来保证volatile的语义。

    • dys
    • 2013/01/22 9:19上午

    没有明白, 数据本来就没有变化,cache 是否失效 没有关系呀

      • he037
      • 2016/02/17 11:30上午

      感觉就是为了符合java规范吧,及时数据没有变化

    • ryo
    • 2014/08/27 9:51下午

    应该为了能够及时读取到最新修改

    • jenny
    • 2016/02/17 9:48上午

    假如两个两个线程同时获取 E oldValue = get(elements, index);
    第一个线程率先修改了elements;第二发现是相同的没有修改,结果第二次set的时候把修改了的又一次set了一遍

    • 江南烟雨
    • 2016/07/27 3:14下午

    是为了维护happens-before规则,具体可以参考下这个帖子:
    http://stackoverflow.com/questions/28772539/why-setarray-method-call-required-in-copyonwritearraylist

    • jacob2014
    • 2020/05/18 2:28下午

    // 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 指令重排,保证了前后读写操的各种内存屏障下,使编译执行的指令逻辑顺序与代码逻辑期望一致。

return top