Будет ли JIT оптимизировать этот код? Требуется ли синхронизация?

#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 секунд. Верно ли мое предположение?