6

当我想知道这个问题时,我担心我正在设计的应用程序中的竞争条件。

假设我有一个由程序的一个组件管理的大型数组或某种集合,我们称该组件为 Monitor。它的工作是定期检查集合是否“脏”,即最近发生了变化,如果是,则将快照写入磁盘(这是在应用程序发生崩溃时检查点)并再次将其标记为干净。

同一程序的其他组件,在不同的线程中运行,调用监视器的方法来添加数据或修改数组/集合中的数据。这些方法将集合标记为脏。

现在,更改方法在其他组件的线程中运行,对吗?如果我不是那么幸运,可能会在将快照写入磁盘时调用它们,更改已写入的数据,设置脏标志,然后监视器的线程将其取消设置,而无需保存更改(它当它改变时已经超过了元素)。所以我有一个被标记为干净的脏集合。

有一段时间,我想我可以通过制作一个临时的集合副本来解决这个问题,把它标记为干净,然后去序列化这个副本。但是复制会是原子的吗,即我可以确定在我复制时集合不会改变吗?

同时,我想我找到了更好的解决方案,比如

  • 在开始写入磁盘之前设置锁定标志,并使数据更改方法等到取消设置标志
  • 让数据更改方法写入“更改队列”而不是直接写入集合,并让线程执行该队列的磁盘写入过程

我认为锁定标志可能是最好的方法。但我还是很好奇:复制变量是原子的吗?


追问:也许这应该是一个问题,但实际上它非常相似。根据下面的答案,我的“锁定标志”方法也可能不起作用,对吧?因为数据更改方法可能会在锁定标志设置为“锁定”值时检查锁定标志并确定它没有被锁定。因此,如果我真的想正确地做到这一点,我需要一个像互斥锁这样的特殊结构,对吗?


感谢埃里克森对我的后续行动的非常有帮助的回答。我真的应该提出这两个问题,所以我可以接受两个答案。请也给他投票。

4

7 回答 7

13

不会。例如,Java 中的长变量在 32 位机器上不是原子的。

此外,还有一个“线程缓存”问题 - 除非您的变量是 volatile 或在同步块内,否则另一个线程可能看不到变量值的更改。这适用于所有类型的变量,而不仅仅是长变量。

在这里阅读:http: //gee.cs.oswego.edu/dl/cpj/jmm.html,尤其是“原子性”和“可见性”段落。

于 2009-01-23T14:45:55.147 回答
6

不,它不是原子的。 请参阅此问题以了解为什么以及如何处理它。

于 2009-01-23T14:43:03.750 回答
4

看看java.util.concurrent.atomic - 里面可能有一些你可以使用的好东西。

于 2009-01-23T14:51:02.610 回答
2

在 JVM 上工作时,您需要关注对其他线程的更改的可见性。通常,您应该在一个synchronized块中进行分配,或者变量应该是volatile,或者您应该使用包中的变量包装器java.util.concurrent.atomic

但是,在您的情况下,听起来您只有一个线程可以清除“脏”标志 - 保留数据的线程。如果是这种情况,请在写入数据之前清除标志。如果其他线程您写入数据时设置它,它将保持设置直到下一次计划写入。我会使用AtomicBoolean, 在检查标志和清除标志之间为您的持久性线程提供原子性,如下所示:

private final AtomicBoolean dirty = new AtomicBoolean();

/**
 * Any method that modifies the data structure should set the dirty flag.
 */
public void modify() {
  /* Modify the data first. */
  ...
  /* Set the flag afterward. */
  dirty.set(true);
}

private class Persister extends Thread {
  public void run() {
    while (!Thread.interrupted()) {
      if (dirty.getAndSet(false)) {
        /* The dirty flag was set; this thread cleared it 
         * and should now persist the data. */
         ...
      }
    }
  }
}
于 2009-01-23T15:49:14.990 回答
1

设置 32 位(至少在 .NET 中)是原子的,但它对您没有好处。您必须阅读它才能知道它是否被锁定,因此您可能会阅读它,并且在阅读之后,其他人在您设置它之前阅读它,因此两个线程最终进入“受保护”代码。这正是实际同步对象(如 .NET Monitor 类)的用途。您也可以使用 Interlocked 来检查和增加锁定变量。

另请参阅:访问 C# 中的变量是原子操作吗?

于 2009-01-23T14:44:44.183 回答
0

很大程度上取决于您正在运行的硬件和 JVM

在某些硬件和某些 JVM 上,某些副本将是原子的,但更安全的是假设情况并非如此,即使是简单的整数到整数赋值也可以转换为 x856 硬件上的四个机器指令。

字符串和数组副本可能涉及数千条指令序列,并且两个线程可以同时更新。

于 2009-01-23T14:53:07.567 回答
-2

据我了解,并非总是如此。

Int32 将是,Int64 不会在 32 位系统上,因为它需要 2 x 32 位。因此,它不适合一个 32 位单元。

于 2009-01-23T14:45:59.267 回答