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

2023-12-07

下面是一个简单的 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 对象(整数是不可变的),尽管代码表明这就是这里发生的情况。但实际上会发生的是 cnt++ 将创建一个值为 cnt + 1 的新 Integer 并覆盖 cnt。 这就是代码实际执行的操作:

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

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 如何创建一个显示 Spinners 的 x 和 y 值的表格?

    我想创建一个位于图表右侧的表格 其中显示 2 列 x 和 y 值已输入到xSpin and ySpin旋转器 我已经画了一张我想要桌子放置的位置的图 我尝试过在网格窗格布局中使用文本框来创建表格并将值直接输入到文本框网格中 但是我无法将它们
  • 如何在不超过最大值的情况下增加变量?

    我正在为学校开发一个简单的视频游戏程序 我创建了一个方法 如果调用该方法 玩家将获得 15 点生命值 我必须将生命值保持在最大值 100 并且由于我目前的编程能力有限 我正在做这样的事情 public void getHealed if h
  • 使用 gcc 在 Linux 上运行线程构建块 (Intel TBB)

    我正在尝试为线程构建块构建一些测试 不幸的是 我无法配置 tbb 库 链接器找不到库 tbb 我尝试在 bin 目录中运行脚本 但这没有帮助 我什至尝试将库文件移动到 usr local lib 但这又失败了 任何的意见都将会有帮助 确定您
  • 如何模拟从抽象类继承的受保护子类方法?

    如何使用 Mockito 或 PowerMock 模拟由子类实现但从抽象超类继承的受保护方法 换句话说 我想在模拟 doSomethingElse 的同时测试 doSomething 方法 抽象超类 public abstract clas
  • 画透明圆,外面填充

    我有一个地图视图 我想在其上画一个圆圈以聚焦于给定区域 但我希望圆圈倒转 也就是说 圆的内部不是被填充 而是透明的 其他所有部分都被填充 请参阅这张图片了解我的意思 http i imgur com zxIMZ png 上半部分显示了我可以
  • std::list 线程push_back、front、pop_front

    std list 线程安全吗 我假设不是这样 所以我添加了自己的同步机制 我认为我有正确的术语 但我仍然遇到问题 每个函数都由单独的线程调用 Thread1 不能等待 它必须尽可能快 std list
  • 匿名类上的 NotSerializedException

    我有一个用于过滤项目的界面 public interface KeyValFilter extends Serializable public static final long serialVersionUID 7069537470113
  • 具有 java XSLT 扩展的数组

    我正在尝试使用 java 在 XSLT 扩展中使用数组 我收到以下错误 Caused by java lang ClassCastException org apache xpath objects XObject cannot be ca
  • 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample

    遵循电子书第 4 3 3 节 PDF 文档的数字签名 https jira nuxeo com secure attachment 49931 digitalsignatures20130304 pdf 我正在尝试创建一个工作示例 其中 客
  • 如何使从 C# 调用的 C(P/invoke)代码“线程安全”

    我有一些简单的 C 代码 它使用单个全局变量 显然这不是线程安全的 所以当我使用 P invoke 从 C 中的多个线程调用它时 事情就搞砸了 如何为每个线程单独导入此函数 或使其线程安全 我尝试声明变量 declspec thread 但
  • Java 中的“Lambdifying”scala 函数

    使用Java和Apache Spark 已用Scala重写 面对旧的API方法 org apache spark rdd JdbcRDD构造函数 其参数为 AbstractFunction1 abstract class AbstractF
  • Javafx过滤表视图

    我正在尝试使用文本字段来过滤表视图 我想要一个文本字段 txtSearch 来搜索 nhs 号码 名字 姓氏 和 分类类别 我尝试过在线实施各种解决方案 但没有运气 我对这一切仍然很陌生 所以如果问得不好 我深表歉意 任何帮助将不胜感激 我
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • 将 Azure AD 高级自定义角色与 Spring Security 结合使用以进行基于角色的访问

    我创建了一个演示 Spring Boot 应用程序 我想在其中使用 AD 身份验证和授权 并使用 AD 和 Spring Security 查看 Azure 文档 我执行了以下操作 package com myapp contactdb c
  • 用于运行可执行文件的python多线程进程

    我正在尝试将一个在 Windows 上运行可执行文件并管理文本输出文件的 python 脚本升级到使用多线程进程的版本 以便我可以利用多个核心 我有四个独立版本的可执行文件 每个线程都知道要访问它们 这部分工作正常 我遇到问题的地方是当它们
  • Java中的Object类是什么?

    什么是或什么类型private Object obj Object http download oracle com javase 6 docs api java lang Object html是Java继承层次结构中每个类的最终祖先 从
  • 具有特定参数的 Spring AOP 切入点

    我需要创建一个我觉得很难描述的方面 所以让我指出一下想法 com x y 包 或任何子包 中的任何方法 一个方法参数是接口 javax portlet PortletRequest 的实现 该方法中可能有更多参数 它们可以是任何顺序 我需要
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • 调整添加的绘制组件的大小和奇怪的摆动行为

    这个问题困扰了我好几天 我正在制作一个特殊的绘画程序 我制作了一个 JPanel 并添加了使用 Paint 方法绘制的自定义 jComponent 问题是 每当我调整窗口大小时 所有添加的组件都会 消失 或者只是不绘制 因此我最终会得到一个
  • 如何在 JFreeChart 中设置多个系列的线条粗细?

    我创建了很多图表 在他们每个人中我都需要打电话 renderer setSeriesStroke i new BasicStroke 2 0f 对于每个系列 renderer is chart getXYPlot getRenderer 我

随机推荐