Сценарий параллелизма Java — мне нужна синхронизация или нет?

Как показано в вашем собственном ответе , PSv3 + использует специальную область $using: , которая позволяет включать значения переменных из области вызывающей стороны . в блоки сценариев, выполненные удаленно - , решает вашу проблему . См. Нижний раздел, если вам нужно решить проблему в PowerShell v2.


Что касается , почему ваш оригинальный подход, основанный на -ArgumentList, не работал :

В то время как блок скрипта, переданный Invoke-Command , работает увидеть все, что было передано через -ArgumentList в его непосредственном объеме, любых командах в нем, которые запускают через блок сценария ({ ... }) - даже если они технически запустите в ту же область , как и в случае с Where-Object и ForEach-Object - , не не видят тот же массив $Args, если он не равен ] явно передается - что не является опцией, когда блоки сценариев передаются в такие командлеты, как Where-Object и ForEach-Object.

Упрощенный пример (в котором даже не используется удаленное взаимодействие, что является следствием исходной проблемы):

Invoke-Command { 

  # OK - sees 'foo', because $Args reflects what was passed via -ArgumentList
  "0: $($Args[0])"

  # !! Does NOT see foo', because the script block executed by ForEach-Object
  # !! - even though it runs in the same scope - has its own $Args definition
  # !! and given that NO arguments were passed to *it*, the $Args that it
  # !! sees is *empty* (an empty array).
  'bar' | ForEach-Object { "1: $($Args[0])" } 

} -ArgumentList 'foo'

Выше приведено:

0: foo
1:

, которое показывает, что Блок сценария, переданный в ForEach-Object, больше не видит то же значение $Args, что и область верхнего уровня блока сценария, переданного в Invoke-Command.


Вы можете заставить свой оригинальный подход работать следующим образом (что необходимо, если вы застряли в PowerShell v2):

Invoke-Command { 

  # OK - sees 'foo', because $Args reflects what was passed via -ArgumentList
  "0: $($Args[0])"

  # Save the original $Args array...
  $ArgsSaved = $Args

  # ... and reference the *saved* array in the script block.
  'bar' | ForEach-Object { "1: $($ArgsSaved[0])" } 

} -ArgumentList 'foo'

16
задан storkman 18 November 2008 в 22:07
поделиться

9 ответов

Используйте Энергозависимый

, действительно ли это - случай, где один поток заботится о том, что другой делает? Тогда JMM FAQ имеет ответ:

Большую часть времени, один поток не заботится о том, что другой делает. Но когда это делает, это - то, для чего синхронизация.

В ответ на тех, кто говорит, что код OP безопасен как есть, рассмотрите это: нет ничего в модели памяти Java, которая гарантирует, что это поле будет сброшено к оперативной памяти, когда новая дискуссия будет начата. Кроме того, JVM свободна переупорядочить операции, пока изменения не обнаруживаемы в потоке.

Теоретически разговор, потоки читателя, как гарантируют, не будут видеть "запись" к validProgramCodes. На практике они в конечном счете будут, но Вы не можете быть уверены когда.

я рекомендую объявить validProgramCodes участника как "энергозависимого". Разность оборотов будет незначительна, и она гарантирует безопасность Вашего кода теперь и в будущем, независимо от того, что оптимизация JVM могла бы быть представлена.

Вот конкретная рекомендация:

import java.util.Collections;

class Metadata {

    private volatile Map validProgramCodes = Collections.emptyMap();

    public Map getValidProgramCodes() { 
      return validProgramCodes; 
    }

    public void setValidProgramCodes(Map h) { 
      if (h == null)
        throw new NullPointerException("validProgramCodes == null");
      validProgramCodes = Collections.unmodifiableMap(new HashMap(h));
    }

}

Неизменность

В дополнение к обертыванию его с unmodifiableMap, я копирую карту (new HashMap(h)). Это делает снимок, который не изменится, даже если вызывающая сторона метода set продолжит обновлять карту "h". Например, они могли бы очистить карту и добавить новые записи.

Зависят от Интерфейсов

На стилистической ноте, часто лучше объявить API с абстрактными типами как List и Map, а не бетон вводит как ArrayList, и HashMap. Это дает гибкость в будущем, если конкретные типы должны измениться (как я сделал здесь).

Кэширование

результатом присвоения "h" к "validProgramCodes" может просто быть запись к кэшу процессора. Даже когда новый поток запускается, "h" не будет видим к новому потоку, если это не было сброшено к общей памяти. Хорошее время выполнения постарается не сбрасывать, если это не будет необходимо, и использование volatile является одним способом указать, что это необходимо.

Переупорядочение

Принимают следующий код:

HashMap codes = new HashMap();
codes.putAll(source);
meta.setValidProgramCodes(codes);

, Если setValidCodes просто OP's validProgramCodes = h;, компилятор является бесплатным переупорядочить код что-то вроде этого:

 1: meta.validProgramCodes = codes = new HashMap();
 2: codes.putAll(source);

предположим после выполнения строки писателя 1, поток читателя начинает выполнять этот код:

 1: Map codes = meta.getValidProgramCodes();
 2: Iterator i = codes.entrySet().iterator();
 3: while (i.hasNext()) {
 4:   Map.Entry e = (Map.Entry) i.next();
 5:   // Do something with e.
 6: }

Теперь предполагают, что поток писателя называет "putAll" на карте между строкой читателя 2 и строкой 3. Карта, лежащая в основе Итератора, испытала параллельную модификацию и бросает время выполнения exception— ужасно неустойчивое, на вид необъяснимое исключение на этапе выполнения, которое никогда не производилось во время тестирования.

Параллельное Программирование

Любое время у Вас есть один поток, который заботится о том, что делает другой поток, Вы должны иметь своего рода барьер памяти, чтобы гарантировать, что действия одного потока видимы к другому. Если случай в одном потоке должен произойти перед событием в другом потоке Вы должны указывать на это явно. Иначе нет никаких гарантий. На практике это означает volatile или synchronized.

не экономят. Не имеет значения, как быстро неправильной программе не удается сделать ее задание. Примеры, показанные здесь, просты и изобретены, но пребывать в уверенности, они иллюстрируют реальные ошибки параллелизма, которые невероятно трудно определить и решить из-за их непредсказуемости и чувствительности платформы.

Дополнительные Ресурсы

28
ответ дан 30 November 2019 в 16:37
поделиться

Нет, пример кода не безопасен, потому что нет никакого безопасная публикация никаких новых экземпляров HashMap. Без любой синхронизации существует возможность, что поток читателя будет видеть , частично инициализировал HashMap.

объяснение @erickson Выезда при "Переупорядочении" в его ответе. Также я не могу рекомендовать книжный Параллелизм Java Brian Goetz на практике достаточно!

, Является ли это хорошо с Вами, что потоки читателя могли бы видеть старые (устаревшие) ссылки HashMap, или никогда не мог бы даже видеть новую ссылку, не относится к делу. Худшая вещь, которая может произойти, состоит в том, что поток читателя мог бы получить ссылку на и попытаться получить доступ к экземпляру HashMap, который еще не инициализируется и не готовый быть полученным доступ.

4
ответ дан 30 November 2019 в 16:37
поделиться

Нет, Моделью памяти Java (JMM) это не ориентировано на многопотоковое исполнение.

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

, Равно как и упомянуто нет никакой гарантии, что поток reaer будет когда-либо видеть новое значение. На практике с текущими компиляторами на существующих аппаратных средствах значение должно быть обновлено, если тело цикла не sufficienly маленький, что это может быть достаточно встроено.

Так, сделав ссылку volatile соответствует под новым JMM. Это вряд ли будет иметь существенное значение к производительности системы.

мораль этой истории: Поточная обработка является трудной. Не пытайтесь быть умными, потому что иногда (может быть не в Вашей системе тестирования), Вы привычка быть достаточно умным.

3
ответ дан 30 November 2019 в 16:37
поделиться

Как другие уже отметили, это не безопасно, и Вы не должны делать этого. Вам нужно или энергозависимый или синхронизируемый здесь, чтобы вынудить другие потоки видеть изменение.

то, Что не было упомянуто, - то, который синхронизировался, и особенно энергозависимый, вероятно, намного быстрее, чем Вы думаете. Если это будет на самом деле узкое место производительности в Вашем приложении, то я съем эту веб-страницу.

Другая опция (вероятно, медленнее, чем энергозависимый, но YMMV) состоит в том, чтобы использовать ReentrantReadWriteLock для защиты доступа так, чтобы несколько параллельных читателей могли считать его. И если это будет все еще узким местом производительности, я съем этот целый веб-сайт.

  public class Metadata
  {
    private HashMap validProgramCodes;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public HashMap getValidProgramCodes() { 
      lock.readLock().lock();
      try {
        return validProgramCodes; 
      } finally {
        lock.readLock().unlock();
      }
    }

    public void setValidProgramCodes(HashMap h) { 
      lock.writeLock().lock();
      try {
        validProgramCodes = h; 
      } finally {
        lock.writeLock().unlock();
      }
    }
  }
3
ответ дан 30 November 2019 в 16:37
поделиться

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

private volatile HashMap validProgramCodes;

Этот путь при обновлении "указателя" validProgramCodes Вы гарантируете тот весь, потоки получают доступ к тому же последнему HasMap "указатель", потому что они не полагаются на локальный кэш потока и переходят непосредственно к памяти.

2
ответ дан 30 November 2019 в 16:37
поделиться

Присвоение будет работать, пока Вы не обеспокоены чтением устаревших значений, и, пока можно гарантировать, что hashmap правильно заполняется на инициализации. Необходимо самое меньшее создать hashMap с Collections.unmodifiableMap на Hashmap, чтобы гарантировать, что читатели не будут изменяться/удалять объекты из карты, и избегать нескольких потоков, ступающих на каждого пальцы ног других и делающих недействительным итераторы, когда другие потоки уничтожат.

(писатель выше прав относительно энергозависимого, должен был видеть это)

1
ответ дан 30 November 2019 в 16:37
поделиться

В то время как это не лучшее решение для этой конкретной проблемы (идея erickson нового unmodifiableMap), я хотел бы занять одну минуту для упоминания java.util.concurrent. Класс ConcurrentHashMap представлен в Java 5, версии HashMap, конкретно созданного с параллелизмом в памяти. Эта конструкция делает не блок на чтениях.

1
ответ дан 30 November 2019 в 16:37
поделиться

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

По крайней мере, я также объявил бы validProgramCodes быть volatile так, чтобы ссылка не была оптимизирована в регистр или что-то.

-1
ответ дан 30 November 2019 в 16:37
поделиться

Если я читал JLS правильно (никакие гарантии там!), доступы к ссылки являются всегда атомарными, период. См. Раздел 17.7 Неатомарных Обработок двойных и длинных

Так, если доступ к ссылке является всегда атомарным, и не имеет значения, какой экземпляр возвращенного Hashmap потоки видят, необходимо быть в порядке. Вы не будете видеть частичные записи к ссылке, никогда.

<час>

Редактирование: После того, как обзор обсуждения в комментариях ниже и других ответах, здесь является ссылками/кавычками от [1 120]

Doug Lea книга (Параллельное Программирование в Java, 2-м Ed), p 94, разделяет 2.2.7.2 Видимость , объект № 3: "

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

На p. 94, Lea продолжает описывать риски, связанные с этим подходом:

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

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

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

<час>

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

я не удаляю ответ в надежде, что это будет полезно. Я не поиск значка "Давления со стороны окружающих"...;-)

-3
ответ дан 30 November 2019 в 16:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: