【问题标题】:Data race in Java ArrayList classJava ArrayList 类中的数据竞争
【发布时间】:2010-10-01 05:09:00
【问题描述】:

我正在阅读有关 CopyOnWriteArrayList 的信息,并且想知道如何在 ArrayList 类中演示数据竞争。基本上,我试图模拟ArrayList 失败的情况,因此有必要使用CopyOnWriteArrayList。关于如何模拟的任何建议。

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    竞争是两个(或更多)线程尝试对共享数据进行操作,最终输出取决于访问数据的顺序(并且该顺序是不确定的)

    来自维基百科:

    竞态条件或竞态危害是电子系统或流程中的缺陷,其中流程的输出和/或结果出乎意料地严重依赖于其他事件的顺序或时间。该术语起源于两个信号相互竞争以首先影响输出的想法。

    例如:

    public class Test  {
        private static List<String> list = new CopyOnWriteArrayList<String>();
    
        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");
                }
            }
        }
    }
    

    然而,这在使用ArrayListArrayIndexOutOfbounds 时会失败。这是因为在插入之前应该调用ensureCapacity(..) 以确保内部数组可以保存新数据。这就是发生的事情:

    • 第一个线程调用add(..),然后又调用ensureCapacity(currentSize + 1)
    • 在第一个线程实际增加大小之前,第二个线程也调用ensureCapacity(currentSize + 1)
    • 因为两者都读取了currentSize的初始值,所以内部数组的新大小为currentSize + 1
    • 这两个线程进行了昂贵的操作,将旧数组复制到具有新大小的扩展数组中(不能同时容纳两个添加
    • 然后他们每个人都尝试将新元素分配给array[size++]。第一个成功,第二个失败,因为内部数组没有正确扩展,由于rece条件。

    发生这种情况是因为两个线程试图同时在同一个结构上添加项目,并且其中一个的添加覆盖了另一个的添加(即第一个丢失了)

    CopyOnWriteArrayList 的另一个好处

    • 多个线程写入ArrayList
    • 一个线程迭代ArrayList。它肯定会得到ConcurrentModificationException

    演示方法如下:

    public class Test  {
        private static List<String> list = new ArrayList<String>();
    
        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");
                }
            }
        }
    }
    

    如果你多次运行这个程序,你通常会得到ConcurrentModificationException你得到OutOfMemoryError之前。

    如果换成CopyOnWriteArrayList,不会出现异常(但是程序很慢)

    请注意,这只是一个演示 - CopyOnWriteArrayList 的好处是读取次数远远超过写入次数。

    【讨论】:

    • @Mark - 但问题是关于多线程的,这个异常(顾名思义)本质上是“多线程的”。
    • 好像我不太明白。你能告诉我数据竞争是如何引起的吗?
    【解决方案2】:

    例子:

    for (int i = 0; i < array.size(); ++i) {
        Element elm = array.get(i);
        doSomethingWith(elm);
    }
    

    如果另一个线程在这个线程调用array.get(i)之前调用了array.clear(),但是在它比较了i和array.size()之后,-> ArrayIndexOutOfBoundsException。

    【讨论】:

      【解决方案3】:

      两个线程,一个递增arraylist,一个递减。数据竞赛可能发生在这里。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多