2010-04-26 177 views

回答

42

对ArrayList上的两个线程同时调用add时发生的情况没有保证行为。但是,我的经验是两个对象都被添加了。与列表相关的大多数线程安全问题在添加/删除时处理迭代。尽管如此,我强烈建议不要使用具有多线程和并发访问的vanilla ArrayList。

向量曾经是并发列表的标准,但现在的标准是使用Collections synchronized list

另外,如果您打算随时用Java中的线程工作,我强烈推荐Goetz等人在实践中使用Java Concurrency。本书更详细地介绍了这个问题。

+13

“这是我的经验,两个对象已被添加罚款”只是想指出,这只是运气。 ArrayList数据损坏问题的窗口可能非常小,但它仍然存在 – 2010-04-26 20:32:23

+5

对,这就是我的观点。绝大多数情况下不会有问题;但我们不会对大多数情况编程。所以我强烈建议不要使用ArrayList。 – derivation 2010-04-26 21:03:02

2

如果你想要线程安全的arrayList,你可以使用List l = Collections.synchronizedList(new ArrayList());

0

http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html

注意,此实现不是同步的。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则它必须在外部同步。

由于内部没有同步,所以你推理的东西并不合理。

因此,事情变得不同步,带来不愉快和不可预知的结果。

+0

对,看到了。我曾经想过会有一个'ConcurrentModificationException'会被抛出,这对我们来说并没有发生..也许我们遇到了另一个不使用线程安全集合的影响.. – 2010-04-26 18:52:17

+0

@Marcus似乎只是如果你在迭代它的同时修改列表。此外,不保证此异常将被抛出。 – WhirlWind 2010-04-26 18:53:51

1

java.util.concurrent有一个线程安全的数组列表。标准ArrayList不是线程安全的,并且多线程同时更新时的行为未定义。当一个或多个线程同时写入时,多读者也可能会出现奇怪的行为。

3

你也可以得到一个null,一个ArrayOutOfBoundsException,或一些遗留下来的实施。已经观察到在生产系统中进入无限循环。你不需要知道什么可能会出错,只是不要这样做。

您可以使用Vector,但它倾向于解决接口不够丰富。在大多数情况下,你可能会发现你想要一个不同的数据结构。

3

该行为可能是未定义的,因为ArrayList不是线程安全的。如果在Iterator进行交互时修改列表,则会得到ConcurrentModificationException。您可以使用Collection.synchronizedList封装ArrayList,或使用线程安全的集合(有很多),或者将add调用放入同步块中。

14

任何数量的事情都可能发生。您可以正确添加这两个对象。您只能添加其中一个对象。你可能会得到一个ArrayIndexOutOfBounds异常,因为底层数组的大小没有适当调整。或者可能发生其他事情。只要说你不能依赖任何发生的行为就够了。

作为替代方案,您可以使用Vector,您可以使用Collections.synchronizedList,您可以使用CopyOnWriteArrayList,或者您可以使用单独的锁。这一切都取决于你在做什么以及对访问该集合有何种控制。

3

我想出了下面的代码来模仿一些真实的世界场景。

100个任务并行运行,他们将完成状态更新到主程序。我使用CountDownLatch来等待任务完成。

import java.util.concurrent.*; 
import java.util.*; 

public class Runner { 

    // Should be replaced with Collections.synchronizedList(new ArrayList<Integer>()) 
    public List<Integer> completed = new ArrayList<Integer>(); 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     Runner r = new Runner(); 
     ExecutorService exe = Executors.newFixedThreadPool(30); 
     int tasks = 100; 
     CountDownLatch latch = new CountDownLatch(tasks); 
     for (int i = 0; i < tasks; i++) { 
      exe.submit(r.new Task(i, latch)); 
     } 
     try { 
      latch.await(); 
      System.out.println("Summary:"); 
      System.out.println("Number of tasks completed: " 
        + r.completed.size()); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     exe.shutdown(); 
    } 

    class Task implements Runnable { 

     private int id; 
     private CountDownLatch latch; 

     public Task(int id, CountDownLatch latch) { 
      this.id = id; 
      this.latch = latch; 
     } 

     public void run() { 
      Random r = new Random(); 
      try { 
       Thread.sleep(r.nextInt(5000)); //Actual work of the task 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      completed.add(id); 
      latch.countDown(); 
     } 
    } 
} 

,当我跑的应用10倍,至少3〜4次的方案并没有打印的已完成的任务数正确。理想情况下,它应该打印100(如果没有例外发生)。但在某些情况下,它正在打印98,99等。

因此,它证明ArrayList的并发更新不会给出正确的结果。

如果我用同步版本替换ArrayList,程序输出正确的结果。

0

你也可以使用的ArrayList();

Collections.synchronizedList(new ArrayList()); 

new Vector(); 

synchronizedList因为我最好的,因为它是:

  • 快上50-100%
  • 能与已有的Arra合作yList的
相关问题