Эмуляция барьера памяти в Java, чтобы избавиться от изменчивых чтений - PullRequest
7 голосов
/ 11 октября 2019

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

public Object myRef = new Object();

Допустим, поток T1 будет устанавливать myRef другое значение раз в минуту, а N другоеПотоки будут читать myRef миллиарды раз непрерывно и одновременно. Мне только нужно, чтобы myRef в конечном итоге был виден всем потокам.

Простым решением было бы использование AtomicReference или просто изменчивого типа, как это:

public volatile Object myRef = new Object();

Тем не менее, afaik volatile чтения действительно вызываетстоимость исполнения. Я знаю, что это незначительно, это больше похоже на то, что мне интересно, чем на то, что мне действительно нужно. Так что давайте не будем беспокоиться о производительности и предположим, что это чисто теоретический вопрос.

Итак, вопрос сводится к следующему: Есть ли способ безопасно обойти энергозависимые чтения для ссылок, в которые редко пишутся, выполняячто-то на сайте записи?

После некоторого чтения, похоже, что барьеры памяти могут быть тем, что мне нужно. Поэтому, если бы существовала такая конструкция, моя проблема была бы решена:

  • Запись
  • Invoke Barrier (sync)
  • Все синхронизировано, и все потоки увидятновое значение. (без постоянных затрат на чтение сайтов, он может быть устаревшим или нести единовременную стоимость, поскольку кэши синхронизируются, но после этого все возвращается к обычному полю до следующей записи).

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

Я видел, как некоторые примеры кода используют 'волатильным »для аналогичной цели, используя его до контракта. Существует отдельное поле синхронизации, например:

public Object myRef = new Object();
public volatile int sync = 0;

и при написании темы / сайта:

myRef = new Object();
sync += 1 //volatile write to emulate barrier

Я не уверен, что это работает, и некоторые утверждают, что это работает только на архитектуре x86. После прочтения связанных разделов в JMS, я думаю, что гарантированно сработает только в том случае, если эта изменчивая запись связана с изменчивым чтением из потоков, которым необходимо увидеть новое значение myRef. (Так что не избавляйтесь от изменчивого чтения).

Возвращаясь к моему первоначальному вопросу;Это вообще возможно? Возможно ли это на Java? Возможно ли это в одном из новых API в Java 9 VarHandles?

Ответы [ 3 ]

2 голосов
/ 18 октября 2019

Таким образом, вам нужна семантика volatile без затрат времени выполнения.

Я не думаю, что это возможно.

Проблема в том, что стоимость времени выполнения volatile из-за инструкций, которые реализуют барьеры памяти в писателе и коде читателя. Если вы «оптимизируете» читателя, избавившись от барьера памяти, вы больше не гарантируете, что читатель увидит «редко записываемое» новое значение, когда оно действительно записано.

FWIW, некоторые версиикласс sun.misc.Unsafe предоставляет явные методы loadFence, storeFence и fullFence, но я не думаю, что их использование даст какой-либо выигрыш в производительности по сравнению с использованием volatile.


Гипотетически ...

вы хотите, чтобы один процессор в многопроцессорной системе мог сообщать всем остальным процессорам:

"Эй! Что бы вы ни делали, сделайте недействительным кеш-память для адреса XYZ и сделайте это сейчас. "

К сожалению, современные ISA не поддерживают это.

На практике каждыйПроцессор контролирует свой кеш.

1 голос
/ 18 октября 2019
  • Вы можете использовать ReentrantReadWriteLock, который предназначен для сценария нескольких операций чтения-записи.
  • Вы можете использовать StampedLock, которыйпредназначен для одного и того же случая: мало записей, много чтений, но можно и с оптимистичной попытки выполнить чтение. Пример:

    private StampedLock lock = new StampedLock();
    
    public void modify() {            // write method
        long stamp = lock.writeLock();
        try {
          modifyStateHere();
        } finally {
          lock.unlockWrite(stamp);
        }
    } 
    
    public Object read() {            // read method
      long stamp = lock.tryOptimisticRead();
      Object result = doRead();       //try without lock, method should be fast
      if (!lock.validate(stamp)) {    //optimistic read failed
        stamp = lock.readLock();      //acquire read lock and repeat read
        try {
          result = doRead();
        } finally {
          lock.unlockRead(stamp);
        }
      }
      return result;
    }
    
  • Сделайте ваше состояние неизменным и разрешите контролируемые изменения только путем клонирования существующего объекта и изменения только необходимых свойств через конструктор,Как только новое состояние создано, вы назначаете его ссылке, читаемой многими читающими нитями. Таким образом, чтение потоков обходится без затрат .

1 голос
/ 18 октября 2019

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

Создайте класс, который обертывает атрибут ArrayBlockingQueue. У класса есть метод обновления и метод чтения. Метод update отправляет новое значение в очередь и удаляет все значения, кроме последнего. Метод read возвращает результат операции просмотра в очереди, т.е. читает, но не удаляет. Потоки, рассматривающие элемент в начале очереди, делают это беспрепятственно. Потоки, обновляющие очередь, делают это чисто.

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