Использование volatile для обеспечения видимости общих (но не одновременных) данных в Java

Я пытаюсь реализовать быструю версию LZ77, и у меня есть вопрос к параллельному программированию.

Сейчас у меня есть 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 просто оборачивает изменяемое поле и предоставляет два метода: один для чтения и один для записи в него.

Код run() работника:

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(), Мне сказали, что запись в изменчивое поле сбрасывает в память все ранее измененные данные, а затем читает их из другого потока, делая их видимыми.

Так достаточно ли в этом случае обеспечения правильной видимости? Реального одновременного доступа к одним и тем же областям памяти никогда не бывает, поэтому энергозависимое поле должно быть намного дешевле, чем ReadWriteLock.

1 ответ

Решение

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

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

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

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

PS

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

Другие вопросы по тегам