Java ArrayList类中的数据竞争
我正在阅读有关CopyOnWriteArrayList
,并想知道如何在ArrayList
类中演示数据竞争。 基本上我正在尝试模拟ArrayList
失败的情况,因此有必要使用CopyOnWriteArrayList
。 关于如何模拟这个的任何建议。
竞争是两个(或更多)线程尝试对共享数据进行操作,最终输出取决于访问数据的顺序(并且该顺序是不确定的)
来自维基百科:
竞赛条件或竞赛危险是电子系统或过程中的缺陷,其中过程的输出和/或结果出乎意料地且严重地取决于其他事件的顺序或时间。 该术语起源于两个信号相互竞争以首先影响输出的想法。
例如:
public class Test { private static List list = new CopyOnWriteArrayList (); public static void main(String[] args) throws Exception { ExecutorService e = Executors.newFixedThreadPool(5); e.execute(new WriterTask()); e.execute(new WriterTask()); e.execute(new WriterTask()); e.execute(new WriterTask()); e.execute(new WriterTask()); e.awaitTermination(20, TimeUnit.SECONDS); } static class WriterTask implements Runnable { @Override public void run() { for (int i = 0; i < 25000; i ++) { list.add("a"); } } } }
但是,使用ArrayList
和ArrayIndexOutOfbounds
时,这会失败。 那是因为在插入之前应该调用ensureCapacity(..)
以确保内部数组可以保存新数据。 这是发生的事情:
- 第一个线程调用
add(..)
,然后调用ensureCapacity(currentSize + 1)
- 在第一个线程实际增加大小之前,第二个线程也调用
ensureCapacity(currentSize + 1)
。 - 因为两者都读取了
currentSize
的初始值,所以内部数组的新大小是currentSize + 1
- 两个线程进行昂贵的操作,将旧数组复制到扩展数组中,使用新的大小( 不能同时保存两个 )
- 然后每个人都尝试将新元素分配给
array[size++]
。 第一个成功,第二个失败,因为内部arrays由于接收条件而没有正确扩展。
发生这种情况,因为两个线程试图在同一个结构上同时添加项目,并且其中一个线程的添加覆盖了另一个线程的添加(即第一个丢失)
CopyOnWriteArrayList
另一个好处
- 多个线程写入
ArrayList
- 一个线程迭代
ArrayList
。 肯定会得到ConcurrentModificationException
以下是如何演示它:
public class Test { private static List list = new ArrayList (); public static void main(String[] args) throws Exception { ExecutorService e = Executors.newFixedThreadPool(2); e.execute(new WriterTask()); e.execute(new ReaderTask()); } static class ReaderTask implements Runnable { @Override public void run() { while (true) { for (String s : list) { System.out.println(s); } } } } static class WriterTask implements Runnable { @Override public void run() { while(true) { list.add("a"); } } } }
如果多次运行此程序, 在获取OutOfMemoryError
之前 ,通常会收到ConcurrentModificationException
。
如果用CopyOnWriteArrayList
替换它,则不会出现exception(但程序非常慢)
请注意,这只是一个演示 - CopyOnWriteArrayList
的好处是当读取次数大大超过写入次数时。
例:
for (int i = 0; i < array.size(); ++i) { Element elm = array.get(i); doSomethingWith(elm); }
如果另一个线程在此线程调用array.get(i)之前调用array.clear(),但是在将i与array.size(), - > ArrayIndexOutOfBoundsException进行比较之后。
两个线程,一个递增arraylist,一个递减。 数据竞争可能发生在这里。