#java #thread-safety #jit
#java #потокобезопасность #jit
Вопрос:
Ниже приведен класс, который содержит сопоставление терминов с орфографическими ошибками и с правильным написанием. Карта периодически обновляется заданием quartz с помощью вызова updateCache(). Метод updatecache обрабатывает ключ и значения во входной карте и сохраняет их во временном объекте map. После завершения обработки (после цикла for) он присваивает временную карту локальной переменной класса misspelledToCorrectlySpelled.
package com.test;
импортируйте java.util.HashMap;
импортируйте java.util.Map;
импортируйте org.checkthread.annotations.Потокобезопасный;
@ThreadSafe открытый класс SpellCorrectListCacheManager {
private Map<String, String> misspelledToCorrectlySpelled =
new HashMap<String, String>(0);
/*
* invoked by a quartz job thread
*/
public void updateCache(Map<String, String> map) {
Map<String, String> tempMap = new HashMap<String, String>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
//process key and values
String key = entry.getKey().toLowerCase();
String value = entry.getValue().toLowerCase();
tempMap.put(key, value);
}
// update local variable
this.misspelledToCorrectlySpelled = tempMap;
}
/*
* Could be invoked by *multiple* threads
*/
public Map<String, String> getMisspelledToCorrectlySpelled() {
return misspelledToCorrectlySpelled;
}
}
Вопрос 1: Будет ли JIT optimize оптимизировать этот код?
Фактический код
/*
* since tempMap is assigned to misspelledToCorrectlySpelled and not
* used anywhere else, will JIT remove tempMap as shown in the optimized
* version below?
*/
Map<String, String> tempMap = new HashMap<String, String>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
// process key and values
String key = entry.getKey().toLowerCase();
String value = entry.getValue().toLowerCase();
tempMap.put(key, value);
}
this.misspelledToCorrectlySpelled = tempMap;
Оптимизированный код
this.misspelledToCorrectlySpelled = new HashMap<String, String>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
//process key and values
String key = entry.getKey().toLowerCase();
String value = entry.getValue().toLowerCase();
this.misspelledToCorrectlySpelled.put(key, value);
}
Вопрос 2: Предполагая, что JIT не будет оптимизировать код, следует ли синхронизировать метод getMisspelledToCorrectlySpelled?
/*
* is this assignment atomic operation?
*
* Does this needs to be synchronized?
*
* By not synchronizing, the new map may not
* be visible to other threads *immediately* -- this is
* ok since the new map will be visible after a bit of time
*
*/
this.misspelledToCorrectlySpelled = tempMap;
}
Ответ №1:
Вы должны использовать AtomicReference для хранения новой карты, чтобы избежать проблем с синхронизацией и видимостью. Но самая большая проблема в вашем коде заключается в том, что вы предоставляете доступ к не потокобезопасной изменяемой карте нескольким потокам. Вы должны обернуть свою карту в неизменяемую карту :
private AtomicReference<Map<String, String>> misspelledToCorrectlySpelled =
new AtomicReference<Map<String, String>>(Collections.unmodifiableMap(new HashMap<String, String>(0)));
/*
* invoked by a quartz job thread
*/
public void updateCache(Map<String, String> map) {
Map<String, String> tempMap = new HashMap<String, String>(map.size());
for (Map.Entry<String, String> entry : map.entrySet()) {
//process key and values
String key = entry.getKey().toLowerCase();
String value = entry.getValue().toLowerCase();
tempMap.put(key, value);
}
// update local variable
this.misspelledToCorrectlySpelled.set(Collections.unmodifiableMap(tempMap));
}
/*
* Could be invoked by *multiple* threads
*/
public Map<String, String> getMisspelledToCorrectlySpelled() {
return misspelledToCorrectlySpelled.get();
}
Отвечая на ваш вопрос об оптимизации JIT: нет, JIT не удалит использование временной карты.
Комментарии:
1. спасибо за ваши комментарии. Ваше предложение обернуть map в неизменяемую карту отличное — я добавил его. Если имеющийся у меня код многопоточно безопасен (кроме проблемы устаревания / видимости при неиспользовании синхронизации) Я не хочу использовать AtomicReference или Volatile. Видите ли вы проблему, отличную от устаревшей, из-за того, что не используется AtomicReference?
2. пожалуйста, смотрите «Листинг 8. Оптимизированный код из листинга 7» в этой статье ibm.com/developerworks/java/library/j-dcl/index.html Как я могу быть уверен, что JIT не оптимизирует код, удалив tempMap из моего кода? Есть ли способ проверить, удалил ли JIT tempMap?
Ответ №2:
Синхронизация требуется там, где требуется синхронизация.Ничего о JIT нельзя предположить, за исключением того, что совместимая реализация будет соответствовать модели памяти JLS и Java и будет соблюдать код, разработанный с учетом этих правил. (Существует несколько методов синхронизации, не все используют synchronized
ключевое слово.)
Здесь требуется синхронизация, если только не «нормально», что отображается устаревшая версия. (Скорее всего, здесь дело не в этом, и это может быть очень устаревшая версия с кэшами и всем прочим — так что не на что ставить!). Само «присвоение ссылки» является атомарным, поскольку не может произойти «частичная запись», но не гарантируется, что оно [немедленно] будет распространено («видимым») по всем потокам.
Удачного кодирования.
Комментарии:
1. спасибо за ваши комментарии. Я согласен с тем, что вижу устаревшую версию — я предполагаю, что более новая версия будет видна всем потокам максимум через 10 секунд. Верно ли мое предположение?