使用同步块的Java中的并发性未给出预期结果

下面是一个简单的java程序。 它有一个名为“cnt”的计数器,它会递增,然后添加到名为“monitor”的List中。 “cnt”由多个线程递增,并且值被多个线程添加到“monitor”。

在方法“go()”的末尾,cnt和monitor.size()应该具有相同的值,但它们不具有相同的值。 monitor.size()确实有正确的值。

如果通过取消注释其中一个已注释的同步块来更改代码,并注释掉当前未注释的块,则代码会生成预期结果。 此外,如果将线程计数(THREAD_COUNT)设置为1,则代码会生成预期结果。

这只能在具有多个真实核心的机器上重现。

public class ThreadTester { private List monitor = new ArrayList(); private Integer cnt = 0; private static final int NUM_EVENTS = 2313; private final int THREAD_COUNT = 13; public ThreadTester() { } public void go() { Runnable r = new Runnable() { @Override public void run() { for (int ii=0; ii<NUM_EVENTS; ++ii) { synchronized( monitor) { synchronized(cnt) { // <-- is this synchronized necessary? monitor.add(cnt); } // synchronized(cnt) { // cnt++; // <-- why does moving the synchronized block to here result in the correct value for cnt? // } } synchronized(cnt) { cnt++; // <-- why does moving the synchronized block here result in cnt being wrong? } } // synchronized(cnt) { // cnt += NUM_EVENTS; // <-- moving the synchronized block here results in the correct value for cnt, no surprise // } } }; Thread[] threads = new Thread[THREAD_COUNT]; for (int ii=0; ii<THREAD_COUNT; ++ii) { threads[ii] = new Thread(r); } for (int ii=0; ii<THREAD_COUNT; ++ii) { threads[ii].start(); } for (int ii=0; ii<THREAD_COUNT; ++ii) { try { threads[ii].join(); } catch (InterruptedException e) { } } System.out.println("Both values should be: " + NUM_EVENTS*THREAD_COUNT); synchronized (monitor) { System.out.println("monitor.size() " + monitor.size()); } synchronized (cnt) { System.out.println("cnt " + cnt); } } public static void main(String[] args) { ThreadTester t = new ThreadTester(); t.go(); System.out.println("DONE"); } } 

好的,让我们来看看你提到的不同可能性:

1。

 for (int ii=0; ii 

首先,监视器对象在线程之间共享,因此对它进行锁定(这就是synchronized所做的)将确保块内的代码一次只能由一个线程执行。 因此,外部的2内部同步不是必需的,无论如何代码都受到保护。

2。

 for (int ii=0; ii 

好的,这个有点棘手。 cnt是一个Integer对象,Java不允许修改Integer对象(整数是不可变的),即使代码表明这是在这里发生的事情。 但实际上会发生的是cnt ++将创建一个值为cnt + 1并覆盖cnt的新Integer。 这就是代码实际执行的操作:

 synchronized(cnt) { Integer tmp = new Integer(cnt + 1); cnt = tmp; } 

问题是,一个线程将创建一个新的cnt对象,而所有其他线程都在等待锁定旧的cnt对象。 线程现在释放旧的cnt,然后尝试获取新cnt对象的锁定并获取它,而另一个线程获取旧cnt对象的锁定。 突然,2个线程处于临界区,执行相同的代码并导致竞争条件。 这是错误结果的来源。

如果删除第一个同步块(带有监视器的块),那么结果会更加错误,因为竞争的可能性会增加。

通常,您应该尝试仅对最终变量使用synchronized来防止这种情况发生。