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 传递给子线程。我之前列出的代码是遗留代码,我想修复此代码。
答案 0 :(得分:2)
正如David Wallace指出的那样,您至少需要将mainList
声明为volatile
。
然而,仅凭这一点并不能使代码线程安全。即使您在cmd
线程中切换引用,主线程可能已经在此之前获取了引用,然后可以在cmd
的同时继续处理它。线程从中读取。
例如,这是一系列可能的事件:
cmd
主题获取mainList
引用并获取列表A mainList
引用,并获取列表A cmd
线程创建新列表B,并将其分配给mainList
cmd
线程开始迭代列表A cmd
线程继续迭代修改后的列表,由于并发修改现在处于不一致状态编辑:此时,我打算编辑一个建议来做你想做的事,但我意识到你可能想要用这个代码做几件完全不同的事情,所以无论如何,一个建议只是猜测。如果您需要解决方案,我建议您开始一个新问题,详细描述您的目标。
答案 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,我怀疑您的测试并不真正代表生产中发生的事情。