这段Java代码线程安全吗?

时间:2014-08-30 01:34:00

标签: java multithreading thread-safety

public class TestConcurrentForList {

List<Integer> mainList = new ArrayList<Integer>();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
Random r = new Random();

public void start() throws InterruptedException {
    Runnable cmd = new Runnable() {
        @Override
        public void run() {
            List<Integer> tempList = mainList;
            mainList = new ArrayList<Integer>();
            for (Integer i: tempList) {
                System.out.println("subThread:" + i);
            }
        }
    };
    scheduledExecutorService.scheduleAtFixedRate(cmd, 1, 1, TimeUnit.MILLISECONDS);
    while (true) {
        mainList.add(r.nextInt(200));
        Thread.sleep(100);
    }
}

public static void main(String[] args) {
    TestConcurrentForList tester = new TestConcurrentForList();
    try {
        tester.start();
    } catch (Exception e) {
        e.printStackTrace();
        System.err.println(e.getMessage());
    }
}

}

我们的部分产品代码喜欢这样,主线程和子线程共享mainList。我运行测试serval时间但从不重现ConcurrentModificationException。

更新

感谢您的所有回复,此代码实际上是对我们的生产代码的简要抽象。我想做的事情很简单:

主线程保存一个列表以从某些源接收数据,当列表达到一定大小时,主线程将列表传递给子线程,该子线程将数据存储到数据库。 < / p>

可能更安全的方法是提取

List<Integer> tempList = mainList;
mainList = new ArrayList<Integer>();

到主线程,并将 templist 传递给子线程。我之前列出的代码是遗留代码,我想修复此代码。

3 个答案:

答案 0 :(得分:2)

正如David Wallace指出的那样,您至少需要将mainList声明为volatile

然而,仅凭这一点并不能使代码线程安全。即使您在cmd线程中切换引用,主线程可能已经在此之前获取了引用,然后可以在cmd的同时继续处理它。线程从中读取。

例如,这是一系列可能的事件:

  1. cmd主题获取mainList引用并获取列表A
  2. 主线程提取mainList引用,并获取列表A
  3. cmd线程创建新列表B,并将其分配给mainList
  4. 主线程开始计算随机数
  5. cmd线程开始迭代列表A
  6. 主线程将其随机数添加到列表A
  7. cmd线程继续迭代修改后的列表,由于并发修改现在处于不一致状态
  8. 编辑:此时,我打算编辑一个建议来做你想做的事,但我意识到你可能想要用这个代码做几件完全不同的事情,所以无论如何,一个建议只是猜测。如果您需要解决方案,我建议您开始一个新问题,详细描述您的目标。

答案 1 :(得分:0)

不,它不是线程安全的。您没有在mainList周围使用任何同步工具。代码没有抛出ConcurrentModificationException这一事实并不意味着代码是线程安全的。它只是意味着你可能有一个竞争条件,如果它被抛出。

答案 2 :(得分:0)

不,我不认为代码是线程安全的,因为主线程可以在池线程为mainList分配新值时调用List.add。如果mainList在哪里是一个主要的,它可能足以让它变得“易变”。但我不认为你可以使用“易变”的#39;用对象引用。

为了使分配安全,您需要在进行分配时同步某些内容,然后在尝试触摸mainList时进行同步:

  Object lock = new Object();
  ...
        synchronized (lock) {
            mainList = new ArrayList<Integer>();
        }
  ...
        synchronized (lock) {
            mainList.add(r.nextInt(200));
        }

这将确保在主线程处于调用add()的过程中,池线程无法重新分配mainList。

但是我不确定如果只有主线程修改列表并且池线程只迭代元素,是否可以获得ConcurrentModificationException。即使池线程确实修改了列表,如果池线程修改了尚未分配给mainList的新列表,我仍然不确定是否可以获得CME。

因此,如果您看到CME,我怀疑您的测试并不真正代表生产中发生的事情。