并发向数组中add值的解决方案测评
背景
将业务场景抽象如下:
public static void main(String[] args) {
// 使用CompleteFuture并发的向ArrayList中add操作
long start = System.currentTimeMillis();
//CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
ArrayList<Object> list1 = new ArrayList<>();
List<Object> list = Collections.synchronizedList(list1);
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
});
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
});
CompletableFuture<Void> future4 = CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000; i++) {
list.add(i);
}
});
CompletableFuture.allOf(future1, future2, future3, future4).join();
System.out.println(list.size());
long end = System.currentTimeMillis();
System.out.println(end-start);
}
如果不加锁
最后add的元素应该是4W个,但是实际的大小只有1W多不到2W;大量数据会丢失。
加锁-vector
Vector类实现了可扩展的对象数组,并且它是线程安全的。Vector的解决方案很简单,它采用了同步关键词synchronized修饰所有方法。
public void add(int index, E element) {
insertElementAt(element, index);
}
...
// 使用了synchronized关键词修饰
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
加锁-synchronizedList
他的读和写都加锁了;
他继承了SynchronizedList
List<Object> list = Collections.synchronizedList(new ArrayList<>());
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
加锁-CopyOnWriteArrayList
写时复制,加的是ReentrantLock,只能有一个线程去写去复制;
性能很差,因为复制了一份副本。不适用于疯狂写入的场景,适合读多写少。
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();
}
}
总结
对于四个业务线程做处理来疯狂写入的操作,应该用vector或者synchronizedList.