Java потокобезопасный только для записи hashmap
В моем классе Java я включаю Hashmap
переменная (свойство класса) и запустить несколько потоков, которые только для записи в этом HashMap
с помощью put()
: каждый раз, когда происходит запись, он сохраняет уникальный ключ (что сделано по замыслу).
Это synchronized
Ключевое слово в методе класса только для записи достаточно для безопасных условий? Мой HashMap прост и не ConcurrentHashMap
?
3 ответа
Нет, недостаточно только синхронизировать записи. Синхронизация должна применяться как к чтению, так и к записи в память.
Некоторому другому потоку где-то когда-нибудь понадобится прочитать карту (в противном случае, зачем нужна карта?), И этот поток должен быть синхронизирован для правильного просмотра памяти, представленной картой. Они также должны быть синхронизированы, чтобы избежать сбоев из-за временных несоответствий в состоянии карты при ее обновлении.
Чтобы предоставить гипотетический пример, предположим, что Поток 1 записывает хэш-карту, эффекты которой хранятся только в кэш-памяти уровня 1 ЦП 1. Затем поток 2 становится доступным для запуска через несколько секунд и возобновляется на ЦП 2; он считывает хэш-карту, которая поступает из кэша уровня 1 ЦП 2 - он не видит записи, сделанные потоком 1, потому что между записью и чтением не было никакой операции барьера памяти как в записи, так и в чтении потока. Даже если Поток 1 синхронизирует записи, тогда, хотя эффект записи будет сброшен в основную память, Поток 2 все равно не увидит их, потому что чтение было получено из кэша уровня 1. Таким образом, синхронизация записи только предотвращает конфликты при записи.
Помимо кэширования процессора, JMM позволяет потокам самостоятельно кэшировать данные, которые нужно только сбросить в основную память с барьером памяти (синхронизация, энергозависимость с некоторыми особыми ограничениями или завершение построения неизменяемого объекта в JMM 5+).
Чтобы полностью понять эту сложную тему многопоточности, вы должны исследовать и изучать модель памяти Java, и это влияет на обмен данными между потоками. Вы должны понимать концепции отношений "происходит раньше" и видимости памяти, чтобы понять сложности совместного использования данных в современном мире многоядерных процессоров с различными уровнями кэширования.
Если вы не хотите тратить время на понимание JMM, простое правило состоит в том, что два потока должны где-то / как-то синхронизироваться на одном и том же объекте между операциями записи и чтения для одного потока, чтобы увидеть влияние операций другого., Период. Обратите внимание, что это не означает, что все записи и чтения на объекте должны быть синхронизированы сами по себе; Разрешается создавать и настраивать объект в одном потоке, а затем "публиковать" его в других потоках, если поток публикации и потоки выборки синхронизируются на одном и том же объекте для передачи.
Вы можете просто добавить synchronized
модификатор подписи вашего метода и все должно быть в порядке. Я сделал быстрый пример, чтобы показать вам это в действии. Вы можете изменить цикл, чтобы установить столько потоков, сколько хотите.
Попробую добавить тот же ключ n
раз, и если у вас есть проблема с параллелизмом, на карте должны быть дубликаты ключей.
class MyMap{
private Map<String, Object> map;
public MyMap(){
map = new HashMap<String, Object>();
}
public synchronized void put(String key, Object value){
map.put(key, value);
}
public Map<String, Object> getMap(){
return map;
}
}
class MyRunnable implements Runnable{
private MyMap clazz;
public MyRunnable(MyMap clazz){
this.clazz = clazz;
}
@Override
public void run(){
clazz.put("1", "1");
}
}
public class Test{
public static void main(String[] args) throws Exception{
MyMap c = new MyMap();
for(int i = 0 ; i < 1000 ; i ++){
new Thread(new MyRunnable(c)).start();
}
for(Map.Entry<String, Object> entry : c.getMap().entrySet()){
System.out.println(entry);
}
}
}
synchronized
Метод write достаточен для безопасности потока, если:
- Ни один другой метод вашего класса не позволяет изменять базовую хеш-таблицу;
- Базовая хеш-таблица не раскрывается никоим образом, поэтому ее нельзя изменить своими собственными методами (легко: создать частный экземпляр);
- Все методы, которые читают хеш-таблицу, также синхронизируются, если используются одновременно с методом записи. Представьте, что может произойти, если
get()
Вызывается, когда хеш-карта изменяется на полпути.
Последний пункт - отстой, если вам нужно читать с вашей хэш-карты одновременно с записью в нее; использование ConcurrentHashMap
в этом случае.
Если у вас есть только несколько одновременных записей в хэш-карту, а затем вы прочитали ее только в одном потоке, ваше решение должно быть в порядке.