Могу ли я предположить, что в многопоточной (Java или .Net) программе копирование переменной является атомарным? - PullRequest
6 голосов
/ 23 января 2009

Я беспокоился о состоянии гонки в приложении, которое я разрабатываю, когда задавался вопросом об этом вопросе.

Допустим, у меня есть большой массив или какая-то коллекция, которой управляет один компонент моей программы, назовем этот компонент Monitor. Его работа состоит в том, чтобы регулярно проверять, является ли коллекция "грязной", т.е. е. недавно изменился, и если это так, запишите моментальный снимок на диск (чтобы проверить приложение в случае сбоя) и пометьте его как «чистый» снова.

Другие компоненты той же программы, работающие в другом потоке, вызывают методы Monitor для добавления или изменения данных в массиве / коллекции. Эти методы помечают коллекцию как грязную.

Теперь методы изменения выполняются в потоках других компонентов, верно? И если мне не так повезет, они могут быть вызваны во время записи снимка на диск, изменить данные, которые уже были записаны, установить грязный флаг, и поток монитора сбрасывает его сразу после этого, не сохранив изменения (это был уже мимо элемента, когда он изменился). Итак, у меня есть грязная коллекция, помеченная как чистая.

Некоторое время я думал, что, возможно, смогу решить проблему, сделав временную копию коллекции, пометив ее как чистую, а затем отправив и сериализовав копию. Но будет ли копирование атомарным, т.е. е. могу ли я быть уверен, что коллекция не изменится, пока я ее копирую?

Между тем, я думаю, что нашел лучшие решения, как

  • установка флага блокировки перед началом записи на диск и заставление методов изменения данных ждать, пока флаг не будет установлен
  • имеет методы изменения данных, которые записывают в "очередь изменений", а не напрямую в коллекцию, и имеют поток, который выполняет процесс записи на диск, который ставит в очередь

и я думаю, что флаг блокировки может быть лучшим способом. Но мне все еще любопытно: копирование переменной атомарно?


Продолжение : Может быть, это должно идти само по себе, но на самом деле это очень похоже. Согласно ответам ниже, мой подход "флаг блокировки" также может не работать, верно? Потому что метод изменения данных может проверить флаг блокировки, пока он установлен на значение «заблокировано», и решить, что он не заблокирован. Поэтому мне нужна специальная конструкция, такая как мьютекс, если я действительно хочу сделать это правильно, верно?


Престижность Эриксон за его очень полезный ответ в моем последующем. Я действительно должен был задать эти два вопроса, чтобы я мог принять два ответа. Пожалуйста, проголосуйте и за него.

Ответы [ 7 ]

13 голосов
/ 23 января 2009

Нет. Например, длинные переменные в Java не являются атомарными на 32-битных машинах.

Кроме того, существует проблема «кеширования потока» - если ваша переменная не является энергозависимой или находится внутри синхронизированного блока, другой поток может не увидеть изменения значения переменной. Это верно для всех типов переменных, а не только для длинных.

Читайте здесь: http://gee.cs.oswego.edu/dl/cpj/jmm.html, особенно параграфы "атомарность" и "видимость".

6 голосов
/ 23 января 2009

Нет, это не атомарно. См. Этот вопрос , почему и что с этим делать.

4 голосов
/ 23 января 2009

Взгляните на java.util.concurrent.atomic - там могут быть некоторые хорошие вещи, которые вы можете использовать.

2 голосов
/ 23 января 2009

Вы должны быть обеспокоены видимостью изменений в других потоках при работе с 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. */
         ...
      }
    }
  }
}
1 голос
/ 23 января 2009

Установка 32-битной (по крайней мере, в .NET) атомарной, но она вам не помогает. Вы должны прочитать его, чтобы узнать, заблокирован ли он, поэтому вы можете прочитать его, и после чтения кто-то еще прочитает его, прежде чем вы сможете установить его, поэтому два потока заканчиваются внутри «защищенного» кода. Это именно то, для чего предназначены реальные объекты синхронизации (например, класс .NET Monitor). Вы также можете использовать Interlocked для проверки и увеличения переменной блокировки.

См. Также: Доступ к переменной в C # является атомарной операцией?

0 голосов
/ 23 января 2009

Очень сильно зависит от оборудования и JVM, которую вы используете

На некоторых аппаратных средствах и некоторых JVM некоторые копии будут атомарными но безопаснее предположить, что это не тот случай, когда даже простое целочисленное присваивание может преобразовать четыре машинные инструкции на оборудовании x856.

Копии строк и массивов могут включать последовательность из тысяч инструкций и два потока могут обновляться одновременно.

0 голосов
/ 23 января 2009

Насколько я понимаю, не всегда.

Будет Int32, Int64 не будет работать в 32-битной системе, так как ему требуется 2 x 32-битной системы. Поэтому он не помещается в одну 32-битную ячейку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...