правильное использование изменчивого ключевого слова - PullRequest
1 голос
/ 10 июня 2010

Я думаю, что у меня есть довольно хорошее представление о ключевом слове volatile в Java, но я думаю о перефакторинге некоторого кода, и я подумал, что было бы неплохо использовать его.

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

Тогда я подумал, почему бы не использовать volatile

, я мог бы определить ссылку на карту как volatile

private volatile HashMap mappings =....;

, а затем в get (или в любом другом месте, где используется переменная mappings), я просто сделал бы локальную копию ссылки:

public Data get(ID id)
{
    HashMap local = mappings;
    //.. look it up using local
}

, а затем фоновый поток просто загрузил быво временную таблицу, а затем поменяйте местами ссылки в классе

HashMap tmp;
//load tmp from DB
mappings = tmp;//swap variables forcing write barrier

Имеет ли этот подход смысл?и это на самом деле потокобезопасным?

Ответы [ 4 ]

2 голосов
/ 10 июня 2010

В существующих ответах на этот вопрос есть некоторая дезинформация.Использование volatile на самом деле является хорошим шагом в обеспечении безопасности потока.См. пункт 3 в Рассеяние мифов о языке программирования Java от Питера Хаггара из IBM.Хаггар дает немного фона и пример, но суть в следующем:

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

Используя volatile, вы гарантируете, что потоки ссылаются на основную память и не используют личные копии переменных, которые вы используетене знаю или не ожидал.

Чтобы ответить на ваш вопрос, тогда: да, ваша стратегия безопасна.

РЕДАКТИРОВАТЬ:
В ответ на другой пост, здесь theРаздел JLS о volatile полях .

1 голос
/ 10 июня 2010

Имеет ли этот подход смысл?и это на самом деле потокобезопасным?

Это имеет смысл, и это потокобезопасно.Во всяком случае, в любом случае.Некоторые вещи для размышления:

  • При обновлении вы позволяете приложению читать старые, устаревшие значения.Это то, что вы хотели?Это хорошо для некоторых приложений, в других случаях вам может потребоваться заблокировать, пока кэш не будет обновлен (FutureTask делает это поведение довольно простым).
  • Когда loadMappingsFromDB() запускается,поток, первоначально вызывающий get(ID), будет блокироваться до завершения обновления.
  • Несколько потоков могут одновременно вызывать checkLoad(), что означает, что если перезагрузка идет медленно и у вас есть несколько потоков, вызывающих get(ID), вы можете закончить с дерьмом одновременных обновлений.Хотя результат будет таким же, это будет пустой тратой системных ресурсов.Простой способ исправить это в вашем текущем коде - это иметь AtomicBoolean, который вы проверяете перед обновлением:

    private final AtomicBoolean isUpdating = new AtomicBoolean(false);
    private void checkLoad()
    {
        if (System.currentTimeMillis() - last_update_time <= TIMEOUT) return;
        if (!isUpdating.compareAndSet(false, true)) return; // already updating
        try {
            loadMappingsFromDB();
        } finally {
            isUpdating.set(false);
        }
    }
    
0 голосов
/ 10 июня 2010

На самом деле, предложенный вами подход имеет смысл даже без использования ключевого слова volatile. Поскольку присвоение ссылки (mappings = tmp;) является атомарной операцией (переводится в одну машинную команду), вероятность несогласованности памяти отсутствует:
http://java.sun.com/docs/books/tutorial/essential/concurrency/atomic.html
Чтение и запись являются атомарными для ссылочных переменных и для большинства примитивных переменных (все типы, кроме long и double).

Идея ключевого слова «volatile» заключается в том, что если вы измените такую ​​переменную в момент времени T, любой другой поток, получающий доступ к ней в момент времени T + x (x> 0), увидит новое значение. В противном случае, даже после изменения значения, все еще есть некоторый период времени, в течение которого другие потоки могут видеть старое значение. Но для вас это не проблема.
редактировать
Та же идея описана в ссылке выше.

0 голосов
/ 10 июня 2010

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

Вы также можете легко использовать метод get(id) и часть loadMappingsFromDB(), которая перезаписывает ссылку (не всю загрузку из БД, а просто переназначение mappings) для синхронизации с той же блокировкой.

Честно говоря, хотя я бы хотел повторно использовать установленную библиотеку кэширования, такую ​​как EhCache, которая имеет функции для фоновой загрузки или загрузки при запуске, так как эти библиотеки, вероятно, уже давно решили все проблемы с синхронизацией, и вы можете вернемся к заботе о логике вашего приложения, а не о низкоуровневой безопасности домашнего кэша.

...