当前位置: 首页 > 面试题库 >

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

华宇
2023-03-14
问题内容

下面是一个简单的Java程序。它具有一个称为“ cnt”的计数器,该计数器递增,然后添加到名为“ monitor”的列表中。“
cnt”由多个线程递增,并且值由多个线程添加到“ monitor”。

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

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

这只能在具有多个真实内核的机器上复制。

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");
    }    
}

问题答案:

好吧,让我们看看您提到的不同可能性:

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

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

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

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

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

问题在于,一个线程将创建一个新的cnt对象,而所有其他线程都在等待获取旧线程的锁。线程现在释放旧的cnt,然后尝试获取新的cnt对象的锁并获取它,而另一个线程获取旧的cnt对象的锁。关键部分中突然出现2个线程,执行相同的代码并导致竞争状态。这就是错误结果的出处。

如果删除第一个同步块(带有监视器的块),则结果会变得更加错误,因为比赛的机会增加了。

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



 类似资料:
  • 当使用并发hashmap时,我是否需要将put环绕在一个同步块上,是否存在竞争条件:

  • 我正在使用Spring data elasticsearch查询我的elastic文档。我的Elasticsearch实体类: 为了简洁起见,我还做了其他事情,比如设置存储库。数据搜索: Spring为上述调用生成的查询: 查询结果: 查看结果集,检查项目名称字段值,如包含方法!它没有检查完整的给定参数<为什么会这样?如何解决这些问题 Add:organizationId和projectName字

  • 问题内容: 我正在尝试使用python scipy模块中的一个非常基本的示例作为方法,但是没有给出预期的结果。我在pylab模式下使用Ipython。 如果我打印数组“ a”和“ b”的内容,它们是相似的。 期望是:(这将在Matlab中转置结果) 问题答案: NumPy有效地反转了数组的形状。如果数组是一维的,则意味着它无效。 在NumPy中,数组 和 实际上是相同的–它们只是空白不同。您可能想

  • 我想根据X列重新划分spark dataframe。假设X列有3个不同的值(X1,X2,X3)。不同值的数量可能会变化。 我希望一个分区包含只有1个X值的记录。也就是说,我想要3个分区,其中1有x=x1的记录,其他的有x=x2,最后一个有x=x3的记录。 我正在做的重新划分 但是,我在DF中的分区并没有像预期的那样出现。由于一个分区为空,第二个分区包含X1的记录,第三个分区同时包含X2和X3的记录

  • 在学习Java线程的同步时,我写了两个线程对同一个int变量进行增加的例子来练习wait方法,却发现最终结果与预期结果不符,具体如下: 这是我的线程类NumPlus1,在线程中会循环输出当前线程名和变量i: 这是我的main方法,在main方法里启动了两个名字为“one”和“two”的线程: 按线程类中的while循环,预期结果是one线程和two线程打印信息中的i会按0,1,2,3,4走,但最终

  • 主要内容:实例下面是一个具有同步功能的多线程示例,这是和上篇文章同样的例子,它依次打印计数器值,每次运行它时,它产生相同的结果。 实例 每次运行此程序时都会产生相同的结果 -