2014-08-30 120 views
1
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()); 
    } 
} 

}我们的产品代码这段Java代码线程安全吗?

部分喜欢此,主线程和子线程共享mainList。我运行测试时间,但从不重现ConcurrentModificationException。

更新:

感谢您的回答道:这段代码实际上是我们的生产代码的简短抽象。我想要做的事情其实很简单:

主线程拥有一个列表来接收来自某些源的数据,当列表达到一定的大小时主线程将列表传递给存储数据到一个子线程的子线程数据库。

也许更安全做到这一点的方法是提取

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

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

+1

你应该明确声明'mainList'是'volatile';否则有可能这不会做你想做的事情。 – 2014-08-30 01:42:47

+0

@DavidWallace要清楚,'volatile'关键字用于表示变量的值将被不同的线程修改。使变量'volatile'不影响代码是否可以被认为是“线程安全的”。 – 2014-08-30 01:52:24

+0

它不是线程安全的,因为当你有多个线程时,你将在'mainList.add(r.nextInt(200))'上产生'ConcurrentModificationException'。 – 2014-08-30 01:59:46

回答

0

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

+0

我不认为这是正确的。在任何时候只有一个线程与'mainList'一起工作,所以不需要同步它。唯一阻止线程安全的是'mainList'没有被声明为'volatile'。在这种情况下,您实际上并不需要线程安全列表。 – 2014-08-30 01:45:24

+1

@DavidWallace:“同步设施”包括'volatile'。另外,即使mainList被声明为volatile,因为引用的“switch”由两个操作组成,同一个数组列表可能由多个线程操作,因此不是线程安全的。 – 2014-08-30 01:50:52

+0

“同步设施”通常指的是可用于同步操作的事物,并且还包括CAS等内容。 – 2014-08-30 01:52:23

0

不,我不认为代码是线程安全的,因为主线程可以调用List.add而池中的线程被分配一个新的值mainList。如果mainList是主要的,它可能足以使其变得“易变”。但我不认为你可以在对象引用中使用'volatile'。

进行转让安全,你需要的东西同步,而你进行分配,然后也不管你尝试触摸mainList,如:

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

这将确保池中的线程不能重新分配mainList,而主线程正在调用add()。

但我不知道,如果你能得到一个ConcurrentModificationException的如果只在主线程修改列表和池线程只能通过迭代的元素。即使池线程修改了列表,我仍然不确定如果池线程修改了尚未分配给mainList的新列表,那么是否可以获得CME。

所以,如果你看到一个CME,我怀疑你的测试并没有真正代表什么在生产中发生的事情。

2

正如David Wallace所指出的那样,您至少需要声明mainListvolatile

然而,单独实际上并没有使代码线程安全的。即使你切换在cmd线程参考,主线程可能已经获取的基准出现这种情况,然后可以继续作为cmd线程读取,同时它来工作之前。

例如,这是事件的可能顺序:

  1. cmd线程获取mainList参考,并得到列出一个
  2. 主线程获取mainList参考,并也得到列出一个
  3. cmd线程创建新列表B,并将其分配给mainList
  4. 主线程开始计算一个随机数
  5. cmd线程开始遍历列表一个
  6. 主线程将它的随机数列出一个
  7. cmd线程继续遍历修改名单,现在处于不一致的状态,由于并发修改

编辑:在这一点上,我打算编辑一个建议,做你想做的事情,但我意识到可能有几个完全不同的事情,你想用这个代码做,所以一个建议只是一个猜测无论如何。如果你想要一个解决方案,我建议你开始一个新的问题,更详细地描述你的目标。

+0

当你说“主线程提取mainList引用”时,你是否指它将引用复制到堆栈以准备调用方法? – 2014-08-30 18:55:13

+0

@DavidWallace:确实如此。在某些时候,它必须从内存中提取到一个寄存器(或者,在JVM字节码中说,一个点['getfield'](http://docs.oracle.com/javase/specs/jvms/se7/ html/jvms-6.html#jvms-6.5.getfield)指令被执行)。在计算随机数之前或之后是否发生这种情况取决于编译器,但无论哪种情况,都有一个从存储器复制的时间窗口。 – Dolda2000 2014-08-30 18:57:16