Я думаю, что у меня есть довольно хорошая идея о volatile
ключевое слово в Java, но я думаю о рефакторинге некоторого кода, и я думал, что это будет хорошая идея использовать его.
у меня есть класс, который является в основном рабочим как Кэш DB. это содержит набор объектов, которые это считало из базы данных, запросов подач на те объекты, и затем иногда обновляет базу данных (на основе тайм-аута). Вот скелет
public class Cache
{
private HashMap mappings =....;
private long last_update_time;
private void loadMappingsFromDB()
{
//....
}
private void checkLoad()
{
if(System.currentTimeMillis() - last_update_time > TIMEOUT)
loadMappingsFromDB();
}
public Data get(ID id)
{
checkLoad();
//.. look it up
}
}
Таким образом, беспокойство - то, что loadMappingsFromDB мог быть высокой операцией задержки, и это не приемлемо, Так первоначально я думал, что мог вращать поток на запуске кэша и затем просто иметь его сон и затем обновить кэш в фоновом режиме. Но затем я должен был бы синхронизировать свой класс (или карта). и затем я просто обменял бы случайную большую паузу на то, что она сделала каждый доступ кэша медленнее.
Затем я думал, почему бы не использовать volatile
я мог определить ссылку карты как энергозависимую
private volatile HashMap mappings =....;
и затем в get
(или где-либо еще который использует переменную отображений), я просто сделал бы локальную копию ссылки:
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
Этот подход имеет смысл? и действительно ли это на самом деле ориентировано на многопотоковое исполнение?
В существующих ответах на этот вопрос есть некоторая дезинформация. Использование volatile
на самом деле является хорошим шагом в обеспечении безопасности потоков. См. пункт 3 в Развенчание мифов о языке программирования Java Питера Хаггара из IBM. Хаггар приводит небольшую предысторию и пример, но суть заключается в следующем:
Итак, как атомарные операции могут быть не потокобезопасными? Основной момент заключается в том, что они действительно могут быть потокобезопасными, но нет никакой гарантии, что это так. Потокам Java разрешено хранить частные копии переменных отдельно от основной памяти.
Используя volatile
, вы гарантируете, что потоки обращаются к основной памяти и не используют частные копии переменных, о которых вы не знаете или не ожидаете.
Отвечая на ваш вопрос: да, ваша стратегия безопасна.
EDIT:
В ответ на другое сообщение, вот раздел JLS о volatile
полях.
Имеет ли смысл такой подход? и действительно ли это потокобезопасно?
Это имеет смысл и является потокобезопасным. Во всяком случае, до некоторой степени. Некоторые вещи, о которых следует подумать:
FutureTask
упрощает такое поведение). loadMappingsFromDB ()
, поток, первоначально вызвавший get (ID)
, блокируется до завершения обновления. Несколько потоков могут вызывать checkLoad ()
одновременно, что означает, что если перезагрузка идет медленно и у вас есть несколько потоков, вызывающих get (ID)
, вы можете получить массу одновременные обновления. Хотя результат будет таким же, это будет пустой тратой системных ресурсов. Легкий способ исправить это в вашем текущем коде - иметь AtomicBoolean
, который вы проверяете перед обновлением:
private final AtomicBoolean isUpdating = new AtomicBoolean (false);
приватная недействительная checkLoad ()
{
если (System.currentTimeMillis () - last_update_time <= TIMEOUT) return;
если (! isUpdating.compareAndSet (false, true)) return; // уже обновляется
пытаться {
loadMappingsFromDB ();
} наконец {
isUpdating.set (ложь);
}
}
На самом деле, предложенный вами подход имеет смысл даже без использования ключевого слова 'volatile'. Поскольку присваивание ссылок (mappings = tmp;
) является атомарной операцией (переводится как одна машинная команда), нет никакой вероятности несогласованности памяти:
http://java.sun.com/docs/books/tutorial/essential/concurrency/atomic.html
Чтение и запись атомарны для ссылочных переменных и для большинства примитивных переменных (все типы, кроме long и double).
Идея ключевого слова 'volatile' заключается в том, что если вы измените такую переменную в момент времени T, то любой другой поток, обращающийся к ней в момент времени T + x (x > 0), увидит новое значение. В противном случае, даже после изменения значения остается некоторый промежуток времени, в течение которого другие потоки могут видеть старое значение. Но это, похоже, не является проблемой для вас.
edit
Та же идея описана в ссылке выше.
Я думаю, что этот общий подход допустим (повторная загрузка кеша в фоновом потоке, без предотвращения доступа к кешу во время загрузки путем загрузки данных в отдельные экземпляры), но я Я не уверен, что на самом деле дает вам объявление ссылки как volatile
.
Вы могли бы так же легко получить метод get (id)
и часть loadMappingsFromDB ()
, которая перезаписывает ссылку (не всю загрузку из БД, а только перезапись). -присвоение отображений
) синхронизировать на одной и той же блокировке.
Честно говоря, я бы рассмотрел возможность повторного использования установленной библиотеки кэширования, такой как EhCache, которая имеет функции для фоновой загрузки или загрузки при запуске, поскольку эти библиотеки, вероятно, уже давно решили все проблемы с синхронизацией, и вы можете вернитесь к беспокойству о логике вашего приложения, а не о низкоуровневой безопасности домашнего кеша.