Java中的并发性使用同步块不能提供预期的结果 [英] Concurrency in Java using synchronized blocks not giving expected results

查看:113
本文介绍了Java中的并发性使用同步块不能提供预期的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

下面是一个简单的java程序。它有一个称为cnt的计数器,递增,然后添加到名为监视器的列表。 cnt由多个线程增加,并且值被多个线程添加到monitor。



在方法go monitor.size()应该有相同的值,但他们不。



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



这只能在具有多个实际内核的计算机上再现。 / p>

  public class ThreadTester {

private List< Integer> monitor = new ArrayList< Integer>();
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 ){//< - 这是否需要同步?
monitor.add(cnt);
}
// synchronized(cnt){
// cnt ++; //< - 为什么将同步块移动到这里导致cnt的正确值?
//}
}
synchronized(cnt){
cnt ++; //< - 为什么在这里移动同步块导致cnt错误?
}
}
// synchronized(cnt){
// cnt + = NUM​​_EVENTS; //< - 在这里移动同步块得到正确的cnt值,没有惊喜
//}
}

};
主题[]主题=新主题[THREAD_COUNT];

for(int ii = 0; ii threads [ii] = new Thread(r);
}
for(int ii = 0; ii threads [ii] .start();
}
for(int ii = 0; ii try {threads [ii] .join(); } catch(InterruptedException e){}
}

System.out.println(两个值都应为:+ 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(monitor){
synchronized(cnt){// <
monitor.add(cnt);
}
synchronized(cnt){
cnt ++; //< - 为什么将同步块移动到这里导致cnt的正确值?
}
}

首先,监视器对象在线程之间共享,因此得到一个锁(这是什么同步)将确保块内的代码只能由一个线程一次执行。所以2外部同步的内部没有必要,代码是保护无论如何。



2。

  for(int ii = 0; ii  synchronized(monitor){
monitor.add(cnt);
}
synchronized(cnt){
cnt ++; //< - 为什么在这里移动同步块导致cnt错误?
}
}

Ok这个有点棘手。 cnt是一个Integer对象,Java不允许修改一个Integer对象(整数是不可变的),即使代码暗示这是发生在这里。但是什么可以发生的是,cnt ++将创建一个新的整数与值cnt + 1和覆盖cnt。
这是代码实际执行的操作:

  synchronized(cnt){
Integer tmp = new Integer (cnt + 1);
cnt = tmp;问题是,虽然一个线程会创建一个新的cnt对象,而所有其他线程都会创建一个新的cnt对象。线程正在等待获得旧的锁。线程现在释放旧的cnt,然后尝试获取对新的cnt对象的锁,并获得它,而另一个线程获得旧的cnt对象上的锁。突然有2个线程在临界区,执行相同的代码并导致竞争条件。这是错误的结果来自哪里。



如果你删除第一个同步块(一个带监视器),那么你的结果会更加错误,因为种族增加。



一般来说,你应该尝试仅对最终变量使用同步,以防止这种情况发生。


Below is a trivial java program. It has a counter called "cnt" that is incremented and then added to a List called "monitor". "cnt" is incremented by multiple threads, and values are added to "monitor" by multiple threads.

At the end of the method "go()", cnt and monitor.size() should have the same value, but they don't. monitor.size() does have the correct value.

If you change the code by uncommenting one of the commented synchronized blocks, and commenting out the currently uncommented one, the code produces the expected results. Also, if you set the thread count (THREAD_COUNT) to 1, the code produces the expected results.

This can only be reproduced on a machine with multiple real cores.

public class ThreadTester {

    private List<Integer> monitor = new ArrayList<Integer>();
    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");
    }    
}

解决方案

Ok let's have a look at the different possibilities you mention:

1.

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?
    }
}

First the monitor object is shared between the threads, therefore getting a lock on it (that is what synchronized does) will make sure that the code inside of the block will only be executed by one thread at a time. So the 2 synchronized inside of the outer one are not necessary, the code is protected anyway.

2.

for (int ii=0; ii<NUM_EVENTS; ++ii) {
  synchronized( monitor) {
    monitor.add(cnt);
  }
  synchronized(cnt) {
    cnt++;              // <-- why does moving the synchronized block here result in cnt being wrong?
  }
}

Ok this one is a little bit tricky. cnt is an Integer object and Java does not allow modifying an Integer object (Integers are immutable) even though the code suggests that this is what is happening here. But what acutally will happen is that cnt++ will create a new Integer with the value cnt + 1 and override cnt. This is what the code actually does:

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

The problem is that while one thread will create a new cnt object while all other threads are waiting to get a lock on the old one. The thread now releases the old cnt and will then try to get a lock on the new cnt object and get it while another thread gets a lock on the old cnt object. Suddenly 2 threads are in the critical section, executing the same code and causing a race condition. This is where the wrong results come from.

If you remove the first synchronized block (the one with monitor), then your result gets even more wrong because the chances of a race increase.

In general you should try to use synchronized only on final variables to prevent this from happening.

这篇关于Java中的并发性使用同步块不能提供预期的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆