Статьи

3 идиомы синхронизации

При тестировании vmlens в проектах с открытым исходным кодом я обнаружил следующие 3 варианта синхронизации. Каждая идиома синхронизации полезна для определенного шаблона доступа:

Схема доступа Идиома синхронизации
Все темы только для чтения или записи Сигнал
Один поток читает и пишет, другой только читает снимок
Все потоки читаются, создавая объект, если он не существует PutIfAbsent

Сигнал

Изменить поведение потока на основе события в другом потоке.

мотивация

В GUI Frameworks, таких как swing, поток событий обрабатывает все пользовательские события. Некоторые события не зависят от текущего состояния других потоков. Примером является событие отмены для длительной операции. Для уведомления рабочих потоков обработчик событий использует идиому сигнала.

Состав

летучий

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

Образец кода

public class WorkerThread extends Thread {

public volatile boolean  canceled;

public void run()
{
while( ! canceled )
{
// do some work
}
}
}

снимок

умысел

Увидеть согласованное состояние объекта.

мотивация

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

Состав

снимок

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

Образец кода

public class CopyOnWriteArrayList {

  final transient ReentrantLock lock = new ReentrantLock();

  // usa a volatile field for the snapshot reference
  private transient volatile Object[] array;


  final Object[] getArray() {
        return array;
   }


 final void setArray(Object[] a) {
        array = a;
   }


    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // clone the object
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // change the local copy
            newElements[len] = e;
            // set the copy as new snapshot
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


    public void forEach(Consumer
  action) {
        if (action == null) throw new NullPointerException();
        // work with the current snapshot
        Object[] elements = getArray();
        int len = elements.length;
        for (int i = 0; i < len; ++i) {
            @SuppressWarnings("unchecked")
            // all read operations are done on this snapshot 
            E e = (E) elements[i];
            action.accept(e);
        }
    }
}

Взято из java.util.concurrent.CopyOnWriteArrayList. Комментарии мои.

Поставить если отсутствует

умысел

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

мотивация

У вас есть карта объектов, доступ к которой осуществляется из множества потоков. Каждый поток ведет себя одинаково, проверяя, существует ли значение для ключа, если не создает значение. У вас есть много одновременных чтений. Чтение не должно быть заблокировано записью в другой ключ.
Например, в веб-приложении языковые форматы хранятся на карте с использованием языка в качестве ключа. Каждый рабочий поток проверяет, доступны ли языковые форматы на карте, если нет, поток создает новый.

Состав

putIfAbsent

Поток пытается получить значение для ключа, вызывающего get, на параллельной карте. Если метод «get» возвращает null, он создает отсутствующее значение и вызывает putIfAbsent.

Образец кода

public Set  getLanguageTagSet(String category) {
    // get the value
    Set tagset = langtagSets.get(category);
    // if value is null create one
       if (tagset == null) {
            tagset = createLanguageTagSet(category);
            // call putIfAbsent
            Set ts = langtagSets.putIfAbsent(category, tagset);
            // if putIfAbsent returns a value a other thread has created a new value in between 
            if (ts != null) {
                tagset = ts;
            }
     }
     return tagset;
    }

Взято из sun.util.locale.provider.JRELocaleProviderAdapter. Комментарии мои.

Вывод

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