Каков наилучший шаблон или метод для загрузки статического кэша? - PullRequest
3 голосов
/ 17 июля 2009

Допустим, у меня есть следующее (предположим, ограничено Java 1.4, поэтому нет универсальных):

public class CacheManager {
    static HashMap states;
    static boolean statesLoaded;

    public static String getState(String abbrev) {
        if(!statesLoaded) {
            loadStates();
        }
        return (String) states.get(abbrev);
    }

    private static void loadStates() {
        //JDBC stuff to load the data
        statesLoaded = true;
    }
}

В многопоточной среде с высокой нагрузкой, такой как сервер веб-приложений, теоретически могут возникнуть проблемы, если> 1 поток попытается получить и загрузить кэш одновременно. (Далее, при условии, что в веб-приложении нет кода запуска для инициализации кэша)

Достаточно ли просто использовать Collections.synchronizedMap, чтобы это исправить? Есть ли у возвращенного synchronizedMap проблемы с производительностью при выполнении get (), если к нему обращается множество потоков?

Или было бы лучше иметь несинхронизированный HashMap и вместо этого синхронизировать метод загрузки или логическую переменную? Я думаю, что если вы синхронизируете один из них, вы можете заблокировать класс.

Например, если метод загрузки был синхронизирован, что, если 2 потока одновременно вводят метод getStates (), и оба видят, что statesLoaded имеет значение false. Первый получает блокировку метода, загружает кэш и устанавливает для StatesLoaded значение true. К сожалению, 2-й поток уже оценил, что statesLoaded был ложным, и переходит к методу загрузки, как только блокировка освобождается. Разве это не пойдет и снова загрузите кеш?

Ответы [ 6 ]

6 голосов
/ 17 июля 2009

Лучший способ загрузить кеш в этом случае - воспользоваться статической инициализацией JVM:

public class CacheManager {
    private static final HashMap states = new HashMap();

    public static String getState(String abbrev) {
        return (String) states.get(abbrev);
    }

    static {
        //JDBC stuff to load the data
    }
}

Кэш будет загружен при первом использовании класса, и поскольку статическая инициализация является поточно-ориентированной, карта будет заполнена безопасно. Любые последующие вызовы для извлечения значений могут выполняться без какой-либо блокировки.

Всегда полезно использовать преимущества статической инициализации, когда это возможно. Это безопасно, эффективно и часто довольно просто.

1 голос
/ 17 июля 2009

Вы должны синхронизировать эту проверку:

if(!statesLoaded) {
    loadStates();
}

Почему? Несколько потоков могут get() на карте без проблем. Однако вам необходимо атомарно проверить флаг statesLoaded, загрузить состояние и установить флаг, проверить его. В противном случае вы можете (скажем) загрузить состояния, но флаг все равно не будет установлен и будет виден как таковой из другого потока.

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

Следовательно, иметь синхронизированную карту недостаточно (это довольно распространенное недоразумение, кстати).

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

0 голосов
/ 28 апреля 2012

+ 1 для контейнера IoC. Используй весну. Создайте класс CacheManager как нестатический и определите CacheManaget в контексте контекста Spring.

1 Нестатическая версия CacheManager

package your.package.CacheManager;

// If you like annotation
@Component
public class CacheManager<K, V> {

    private Map<K, V> cache;

    public V get(K key) {
        if(cache != null) {
            return cache.get(key);
        }
        synchronized(cache) {
            if(cache == null) {
                loadCache();
            }
            return cache.get(key);
        }
    }

    private void loadCache() {
        cache = new HashMap<K, V>();
        // Load from JDBC or what ever you want to load
    }
}

2 Определите bean-компонент CacheManager в контексте Spring или используйте аннотацию @ Service / @ Component (не забудьте определить путь сканирования для аннотаций)

<bean id="cacheManager" class="your.package.CacheManager"/>

3 Внедрите свой компонент кэша в любое удобное для вас время с помощью конфигурации Spring или аннотации @Autowire

<bean id="cacheClient" clas="...">
    <property name="cache" ref="cacheManager"/>
</bean>
0 голосов
/ 17 июля 2009

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

Это означает, что любой поток, вызывающий getState после инициализации кэша, будет «ранним» и будет использовать карту без блокировки.

что-то вроде:

// If we safely know the states are loaded, don't even try to lock
if(!statesLoaded) {
  // I don't even pretend I know javas synchronized syntax :)
  lock(mutex); 
  // This second check makes sure we don't initialize the
  // cache multiple times since it might have changed
  // while we were waiting for the mutex
  if(!statesLoaded) {
    initializeStates();
    statesLoaded = true;
  }
  release(mutex);
}
// Now you should know that the states are loaded and they were only
// loaded once.

Это означает, что блокировка будет задействована только до и во время фактической инициализации.

Если это будет C, я бы также сделал statesLoaded variable энергозависимым, чтобы компилятор оптимизировал вторую проверку. Я не знаю, как ведет себя Java, когда дело доходит до подобных ситуаций, но я бы предположил, что он считает все общие данные, такие как statesLoaded, потенциально грязными при переходе в области синхронизации.

0 голосов
/ 17 июля 2009

что не так с паттерном синглтона?

public class CacheManager {

    private static class SingletonHolder
    {
        static final HashMap states;
        static
        {
            states = new HashMap();
            states.put("x", "y");
        }
    }

    public static String getState(String abbrev) {
        return (String) SingletonHolder.states.get(abbrev);
    }

}
0 голосов
/ 17 июля 2009

Не пытайтесь делать это самостоятельно. Используйте контейнер IoC, такой как Spring или Guide, и получите среду для управления и инициализации Singleton для вас. Это делает ваши проблемы с синхронизацией намного более управляемыми.

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