При тестировании 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. Комментарии мои.
Поставить если отсутствует
умысел
Получить объект из карты в многопоточной среде. Создайте его, если он не существует.
мотивация
У вас есть карта объектов, доступ к которой осуществляется из множества потоков. Каждый поток ведет себя одинаково, проверяя, существует ли значение для ключа, если не создает значение. У вас есть много одновременных чтений. Чтение не должно быть заблокировано записью в другой ключ.
Например, в веб-приложении языковые форматы хранятся на карте с использованием языка в качестве ключа. Каждый рабочий поток проверяет, доступны ли языковые форматы на карте, если нет, поток создает новый.
Состав
Поток пытается получить значение для ключа, вызывающего 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, для определения условий гонки во время разработки и тестирования.