【问题标题】:Java volatile custom object - Full/Deep object visibilityJava volatile 自定义对象 - 完整/深度对象可见性
【发布时间】:2020-05-02 17:47:43
【问题描述】:

我有以下几点:

public class MainClass {
    private final Map<Integer, List<MyCustomObject>> myMap = new HashMap<>();

    public synchronized addMyCustomObject(MyCustomObject customObj) {
        List<MyCustomObject> customObjList = myMap.get(customObj.getId());

        if (customObjList == null) {
            customObjList = new LinkedList<>();
        }
        customObjList.add(customObj);
    }

    public synchronized List<MyCustomObject> getList(int customObjId) {
        return myMap.get(customObjId);
    }
}

public class MyCustomObject {
    private volatile MyCustomObject sonCustomObject;
    private volatile int id;

    public MyCustomObject(MyCustomObject sonCustomObject, int id) {
        this.id = id;
        this.sonCustomObject = sonCustomObject;
    }

    public void changeId(int newId) {
        this.id = newId;
    }

    public int getId() {
        return this.id;
    }

    public void changeSon(MyCustomObject sonCustomObject) {
        this.sonCustomObject.setId(-1);
        this.sonCustomObject = sonCustomObject;
        this.sonCustomObject.setId(this.id);
    }
}

我的问题如下:

  1. 如果给定一个 MyCustomObject,它的 ID 发生了变化,会发生什么?这种变化对其他线程可见吗? (不同步)我认为是的,ID 是 int 并更改了它的引用。
  2. 如果给定一个 MyCustomObject,它的 SON'S id 发生了变化,会发生什么?此更改是否反映到其他线程? (不同步)我认为是的,因为前面的原因
  3. 如果在其 SON 字段中给定 MyCustomObject without volatile 关键字,儿子的 ID 值发生更改,会发生什么情况?此更改是否反映到其他线程?我认为是的,因为 id 是 int 并且会更改其引用。

经验法则: 让我们假设以下经验法则:如果一个复杂对象是共享的,那么当需要互斥时它应该同步,但是对于那些不改变其引用的字段也必须进行同步(即复杂对象而不是像 int 或 String 这样的原语)。这是必需的,因为使用同步,所有更新都被授予由 java 内存模型安全共享。

谢谢

【问题讨论】:

  • Volatile myList 在这里基本上什么都不做,因为您没有重新分配列表,并且波动应用于列表引用,而不是列表的结构或内容: 你也可以把它做成final。
  • 感谢您的回答,所以我实际上并没有正确刷新我的共享对象。我猜只是简单地重新分配同一个对象而不进一步实例化是不够的,对吧?
  • @AndyTurner 我已经更新了我的代码,“myMap”的引用从未改变,但它的内部内容会改变,所有线程都可以看到它吗?
  • 不,HashMapLinkedList 在没有外部同步的情况下都不安全,如其 Javadoc 中所述。
  • 所以这意味着如果没有显式同步,我将永远不会在所有线程中更新值,对吧?因此,如果我知道我的参考不会及时更改,那么 volatile 关键字将毫无用处。所以让我们假设我已经有一个 MyCustomObject 实例,并且我想在它已经添加到地图后更改它的 ID(使 add 方法同步)。在我将此更改放入同步块之前,我的更改不会得到反映,对吗?我的 ID 是不稳定的,它改变了它的引用,所以在这种情况下可能不需要同步,对吧?

标签: java concurrency volatile


【解决方案1】:

您最初的问题:我可以使用 volatile 获取地图的最新更新吗?

希望我理解正确。

想象一下,如果 Thread B 执行 MyCustomObject() 构造函数: 这将使这个MyCustomObject 可用于其他线程(仅当它们遇到并读取 volatile id 时)。 (建议设置 id 是构造函数中的最后一条语句)。但是,根据给出的代码示例,可能有许多具有相同 id 的对象。

想象一下如果thread A 执行addMyCustomObject() 线程 A 将获得最新更新,包括正确构造的 MyCustomObject 实例。

想象一下如果thread C 执行getList() 它可能会或可能不会返回正确的结果。该地图是最终的,因此它在构建时具有正确发布的状态。然而它是一个可变的持有者,问题就在这里。你需要在这里进行同步。

你能让地图也变得易变吗? 是的,你可以,但那会实现什么。试图找出这种实现中与线程相关的问题会发疯。

为什么不推荐这种编码方式? 就并发环境而言,上面的代码并不清楚阅读和遵循。它很脆弱。建议使用同步结构来阐明互斥和可见性。易失性有其用途,例如标志。

注意:Volatile 是为了保证可见性,而锁定/同步是为了保证可见性和原子性。

如果我的解释有谬误,请告诉我。

编辑 1:回答更新后的问题:

Q1:如果给定一个 MyCustomObject,它的 ID 改变了会发生什么?这种变化对其他线程可见吗? (不同步)我认为是的,ID 是 int 并更改了它的引用。

A1:直到 id 被调用线程引用。

Q2:如果给定一个 MyCustomObject,它的 SON'S id 发生了变化,会发生什么?此更改是否反映到其他线程? (不同步)我认为是的,因为前面的原因

A2:同上。

Q3:如果给定一个 MyCustomObject 的 SON 字段中没有 volatile 关键字,儿子的 ID 值发生了变化,会发生什么情况?此更改是否反映到其他线程?我认为是的,因为 id 是 int 并更改了它的引用。

A3:仅在读取 volatile 时再次。当然,可以一次又一次地调用changeSon(),而另一个线程将看不到更新后的 SON。

最好的方法是创建一个多线程测试工具并运行测试用例 N 次。我假设你有一个多核系统。尽量不要使用分配了单核的 VM。

【讨论】:

  • 嗨卡纳,感谢您的回答!我了解此示例中需要同步,这就是为什么我更新了我的问题,更清楚地解释了我的疑问。由于地图是可变的,它的内容应该是同步的。但是即使在管理已经检索到的 MyCustomObject 实例时也应该进行同步吗?如果您可以查看我更新的问题,也许您可​​以更清楚地了解我的疑问。你认为我的经验法则是正确的吗?谢谢!
  • 如果最终地图的内容没有改变,那么一切都是可见的,一切顺利。如果内容是可变的,那么所有这样的保证都是关闭的。
  • 但是如果单个 MyCustomObject 更改其状态,我必须应用同步,对吗?由于这里的重点不是互斥,您建议对每个 MyCustomObject 更改使用同步还是仅对其复杂的内部字段使用同步?例如,我认为更改 MyCustomObject ID 会使新 ID 对其他线程可见而无需同步,因为 ID 是原始且易失的。但是当改变值时需要同步,比如复杂对象而不是原始类型,而不改变它们的引用。这是真的?谢谢
  • 如前所述:Volatile 用于保证可见性,锁定/同步用于可见性和原子性。可以细粒度的锁定和易失性,它甚至可以工作!但结果将是一个泥潭——难以坚持。建议是限制波动。见:ibm.com/developerworks/library/j-jtp06197/index.html
  • 是的,更新后的 id 在读取时可见。但是地图不会以这个更新的 id 作为键。
猜你喜欢
  • 2016-12-23
  • 2011-02-23
  • 1970-01-01
  • 1970-01-01
  • 2017-02-19
  • 1970-01-01
  • 1970-01-01
  • 2020-06-30
  • 1970-01-01
相关资源
最近更新 更多