依据JDK8源码
内存一致性效果:一个线程往List中插入(更新也算插入)元素及其之前的动作 happens-before 其他线程后续访问或者移除该元素的动作(人话是:我只要插入元素,其他线程就能访问到以及删除。)
多线程共享的资源:数组的引用array
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
get操作的弱一致性
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
get操作没有进行同步,导致可能get到被删除或者更新掉的一个元素。
获取元素的操作分2步,第一步获取数组array。这一步因为是volatile变量建立了同步,因此其他线程在此期间对array的写入是可见的。可以读取到array的最新值。
第二步是对该数组取索引。如果在第一步之后第二步之前其他线程删除了这个元素(即修改了array)。那么是不可见的。因为取的是老array。因此读取了一个被删除的一个元素。
这就是get操作下的弱一致性问题
添加元素
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
添加元素 拷贝原来的数组到新的数组上。通过锁来同步保证了原子性与可见性
修改元素
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); //1
}
return oldValue;
} finally {
lock.unlock();
}
}
注意这里的语句1部分,如果老值与新值一样,那么可以不操作直接返回。需要利用setArray(elements)的写入volatile来与后续的访问建立一个Happens before关系。这里可以去掉这个吗???
更新:JDK 11已经去掉了元素相同时候的setArray()。所以是可以去掉的。
删除元素
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
总结
- 获取List中元素为了保证性能,没有加锁。单纯利用volatile来同步,导致存在弱一致性问题(如果get的那个元素被删除或者更新了,读线程是不知道的)
- 修改List例如增加元素,删除元素,修改元素。因为我们采用写时复制的策略,那么修改List分为获取array引用,拷贝原数组到新数组,将新数组引用写入到array。为了保障原子性。我们必须加锁。