最近一直在看并发编程网,这篇文章先记录下这个地方的理解。
上下文环境移步CopyOnWriteArrayList类set方法疑惑?
- /** 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;
- }
可以看见set方法中else判断中看起来似乎有个空操作setArray,将读取的数组又塞回来赋值。
好像这个地方有没有不管对于单线程还是多线程对elements的结果或者读取的element都不造成影响,看了很久才理解一点那么个意思。
这里注意注释中说明,保证volatile写语义,要理解这句话得先理解java中的指令重排和happens before原则。
并发编程网也有很好的几篇文章解释,详细内容移步happens-before俗解和有关“双重检查锁定失效”的说明
这里调用setArray的话主要保证了对于下列情况,set方法happens before于get方法。
线程1 | 线程2 | ||||||||
|
|
然后是这里为什么要将引用指向clone的一个新对象?
对于在set之前得到指向老对象引用的不进行干扰,包括getArray和iterator等,而且CopyOnWriteArrayList在大多为读操作时才使用,写操作性能较差。发布一个不可变对象,是不需要进一步的同步操作,这是CopyOnWrite的核心思路。
上面双重检查锁定失效的问题这里也提一下,主要问题是有可能对象赋值给引用的指令和对象初始化赋值的指令重排序,导致getInstance拿到对象时查看到未成功初始化完的对象属性。如果对单例引用加上volatile修饰符,会保证完成引用赋值时对象构造函数的赋值完成。
延迟初始化单例的方式还有一种更好的思路,采用占位类方式来实现,当使用类的时候才会初始化,而且不会出现并发问题。
public class ResourceFactory{
private static class ResourceHolder{
public static Resource resource = new Resource();
}
public static Resource getResource(){
return ResourceHolder.resource;
}
}
感觉还有些细节需要再深入了解下,如果有理解不对的地方麻烦指出,多谢。