【问题标题】:Caching of instances缓存实例
【发布时间】:2009-07-20 13:02:31
【问题描述】:

我只是对java内存模型有点好奇。

这就是我的想法。

如果我有以下课程

public class Test {

    int[] numbers = new int[Integer.MAX_VALUE]; // kids dont try this at home

    void increment(int ind){
        numbers[ind]++;
    }

    int get(int ind){
        return numbers[ind];
    }
}

有多个读取器 get() 和一个写入器 increment() 线程访问该类。

问题就在这里,为了在每次方法调用后使类保持一致状态,实际上我需要做任何同步吗?

我为什么要问这个问题,我很好奇数组中的元素是否被 JVM 以某种方式缓存,还是仅适用于类成员?如果数组中的成员可以被缓存,有没有办法将它们定义为 volatile ?

谢谢 罗马

【问题讨论】:

  • 大家说的都对,这是纯java内存模式问题,不是同步问题。我知道类的同步可以完成这项工作,或者原子整数数组可以完美地完成这项工作。但我的问题是 JVM 是否会缓存数组的实际成员。这意味着数组 numbers[0] 的成员是否与 volotile 一样好?
  • 如果是单线程就不用担心同步问题了吧?
  • 单写线程,多个阅读器

标签: java multithreading


【解决方案1】:

作为同步这些方法的替代方法,您还可以考虑将 int[] 替换为 AtomicIntegers 数组。这将具有允许同时访问列表中不同元素的好处/缺点(取决于您的应用程序)。

【讨论】:

  • 而且它也比使用同步快 10 倍。
  • @starblue:我想你有基准来支持这个相当大胆的主张?除非线程争用非常高,否则速度会更快。
  • 在这种情况下,starblue 是正确的,因为同步此类的方法会锁定整个数组。更可取的是在你可以适应的地方使用单个 CAS 操作,而不是在某些东西上进行同步
【解决方案2】:

您肯定必须使用某种同步(在您的类或底层数据结构上)以确保数据在方法调用后保持一致状态。考虑以下情况,有两个线程 A 和 B,整数数组最初包含所有零值。

  • 线程 A 调用增量 (0)。后自增操作不是原子的;您实际上可以认为它至少分为三个步骤:
    • 读取当前值;当前值加一;存储值。
  • 线程 B 也调用 increment(0)。如果在线程 A 执行相同操作后不久发生这种情况,则它们都将为数组索引 0 处的元素读取 相同的初始值。
  • 此时,线程 A 和 B 都为它们想要递增的元素读取了一个值“0”。两者都会将值增加到“1”并将其存储回数组的第一个元素中。
  • 因此,只能看到最后写入到数组的线程的工作。

如果你有一个decrement() 方法,情况类似。如果increment() 和decrement() 几乎同时被两个单独的线程调用,则无法确定结果是什么。该值将加一或减一,并且操作不会相互“取消”。

编辑:更新以反映以下 Roman 的 (OP) 评论

对不起,我看错了帖子。我想我理解你的问题,大致如下:

"如果我声明一个数组为volatile, 这是否意味着可以访问其元素 也被视为volatile?”

快速回答是否定的:请参阅this article for more information;这里之前的答案中的信息也是正确的。

【讨论】:

  • 您可能已经忽略了这一点,即多个读者和一个作者都可以访问它。 MRSW 如果有多个作家,你是绝对正确的。如果数组值是否不稳定,仍然没有人给我答案.. :-)
  • 抱歉造成误会,我已经更新了我的帖子,提供了更好的答案。
  • 感谢您的链接,我知道这个网站。必须在那里监督它 :-) 似乎可以缓存数组元素。
【解决方案3】:

是的,允许虚拟机在线程内缓存任何不同步或易失性的字段。为了防止这种情况,您可以将字段标记为 volatile,但它们仍然不是线程安全的,因为 ++ 不是原子操作。将同步关键字添加到方法中,您就安全了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    相关资源
    最近更新 更多