3

Я пытаюсь реализовать быструю версию LZ77, и у меня есть вопрос, чтобы спросить вас о параллельном программировании.Использование volatile для обеспечения видимости общих (но не одновременных) данных в Java

На данный момент у меня есть final byte[] buffer и final int[] resultHolder, оба одинаковой длины. Программа выполняет следующие действия:

  1. Основной поток записывает весь буфер, затем уведомляет о потоках и ждет их завершения.

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

  3. Когда все рабочие паузы, основной поток считывает данные в resultHolder и обновляет буфер, а затем (при необходимости) процесс начинается снова с точки 1.

Важные вещи в менеджере (Основная тема) объявлена ​​следующим образом:

final byte[] buffer = new byte[SIZE]; 
final MemoryHelper memoryHelper = new MemoryHelper(); 
final ArrayBlockingQueue<Object> waitBuffer = new ArrayBlockingQueue<Object>(TOT_WORKERS); 
final ArrayBlockingQueue<Object> waitResult = new ArrayBlockingQueue<Object>(TOT_WORKERS); 
final int[] resultHolder = new int[SIZE]; 

MemoryHelper просто оборачивает летучее поле и предоставляет два метода: один для чтения его и один для записи на него. пробег (код)

работника:

public void run() { 
    try { 
     // Wait main thread 
     while(manager.waitBuffer.take() != SHUTDOWN){ 
      // Load new buffer values 
      manager.memoryHelper.readVolatile(); 

      // Do something 
      for (int i = a; i <= b; i++){ 
       manager.resultHolder[i] = manager.buffer[i] + 10; 
      } 

      // Flush new values of resultHolder 
      manager.memoryHelper.writeVolatile(); 
      // Signal job done 
      manager.waitResult.add(Object.class); 
     } 
    } catch (InterruptedException e) { } 
} 

Наконец, важная часть главного Тема:

for(int i=0; i < 100_000; i++){ 
    // Start workers 
    for (int j = 0; j < TOT_WORKERS; j++) 
     waitBuffer.add(Object.class); 
    // Wait workers 
    for (int j = 0; j < TOT_WORKERS; j++) 
     waitResult.take(); 

    // Load results 
    memoryHelper.readVolatile(); 
    // Do something 
    processResult(); 
    setBuffer(); 
    // Store buffer 
    memoryHelper.writeVolatile(); 
} 

Синхронизация на ArrayBlockingQueue работает хорошо. Я сомневаюсь в использовании readVolatile() и writeVolatile(). Мне сказали, что запись в нестабильное поле сбрасывает в память все ранее измененные данные, после чего чтение из другого потока делает их видимыми.

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

+0

См. Также http://stackoverflow.com/questions/17108541/happens-before-relationships-with-volatile-fields-and-synchronized-blocks-in-jav – Raedwald

ответ

7

Вам не нужно даже volatile здесь, потому что BlockingQueues уже обеспечивает необходимую видимость памяти гарантию:

памяти эффекты непротиворечивость: Как и в других параллельных коллекциях, действия в потоке до размещения объекта в BlockingQueuehappen-before действия после доступа или удаления этого элемента из BlockingQueue в другом потоке.

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

Тем не менее, volatile чтение и запись могут использоваться для обеспечения видимости памяти, если у вас нет явной синхронизации (например, в алгоритмах без блокировки).

П. С.

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

+0

+1 Согласовано. Добавлена ​​ссылка на JLS 17.4.5, в которой описывается отношение «происходить-до». –

+0

Пробовал с CyclicBarrier и код чище. Благодаря! – Shepard