2013-04-03 2 views
3

В моем классе Java я включаю переменную Hashmap (свойство класса) и запускаю некоторые потоки, которые записываются только в HashMap, используя put(): каждый раз при записи происходит сохранение уникального ключа (который производится по дизайну).Java-потокобезопасный hashmap для записи

Является ли ключевое слово synchronized методом класса достаточно для записи только для безопасных условий? Мой HashMap прост, а не ConcurrentHashMap?

+1

Я так считаю, да. Но это зависит от типа метода и от того, что вы собираетесь заблокировать. –

+3

Хэш-запись с записью? Ты никогда не читаешь? Если да, то почему у вас даже есть это? – thejh

+1

Пока карта не доступна никакими другими способами (например, методом доступа), тогда достаточно простой общей блокировки. – Perception

ответ

6

Нет, недостаточно только синхронизировать записи. Синхронизация должна применяться как для чтения, так и для записи в память.

Некоторые другие темы, где-то, когда-нибудь, должны будут прочитать карту (в противном случае, почему есть карта?), И этот поток должен быть синхронизирован для правильного просмотра памяти, представленной картой. Они также должны быть синхронизированы, чтобы избежать отключения кратковременных несоответствий в состоянии карты, поскольку оно обновляется.

Чтобы представить гипотетический пример, предположим, что Thread 1 записывает hashmap, эффекты которого хранятся в кеше 1 уровня CPU 1 только . Затем Thread 2 становится доступным для запуска через несколько секунд и возобновляется на CPU 2; он считывает хэш-карту, которая поступает из кэша уровня 1 ЦП 2 - он не видит записи, сделанные Thread 1, поскольку между записью и чтением не было операции с защитой памяти между и запись и поток чтения , Даже если Thread 1 синхронизирует записи, то, хотя эффект от записи будет сброшен в основную память, Thread 2 все равно их не увидит, потому что чтение произошло из кеша уровня 1. Таким образом, синхронизация записи только предотвращает столкновения на пишет.

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

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

Если вы не хотите тратить время, чтобы понять JMM, простое правило, что два поток должен где-то/как-то синхронизировать на тот же объект между записью и чтением на один поток, чтобы увидеть последствия операций другого. Период. Обратите внимание, что это не означает, что все записи и чтения на объекте должны быть синхронизированы как таковые; законно создавать и настраивать объект в одном потоке, а затем «публиковать» его для других потоков, если поток публикации и выборки потоков синхронизируются на одном и том же объекте для передачи.

1

Вы можете просто добавить модификатор 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); 
     } 
    } 
} 
+1

Нет, это абсолютно не "хорошо"; этот код пронизан недостатками резьбонарезания, прежде всего проблемой разрешения одновременного чтения/записи, а также непоследовательности видимости памяти, несогласованности итерации и т. д. –

+0

Извините, но она связала вопрос с тегом 'writeonly', поэтому, если этот ответ не подходит из-за одновременного чтения, на ваш взгляд. Почему это должно повлиять на вопрос ОФ? –

+0

Потому что нет практического значения в реальной карте только для записи; где-то, когда-нибудь это будет прочитано, и эти чтения должны быть синхронизированы - период. Конец истории. Для этого нет ярлыка. Карта может быть прочитана через 3 дня после записи, и поток чтения может по-прежнему наблюдать за памятью карты неправильно из-за, например, кэширования ЦП. –

1

synchronized метод записи достаточно для безопасности потока до тех пор, как:

  • Ни один другой метод вашего класса не позволяет изменять основной хэш-таблицы;
  • Основополагающая хеш-таблица не подвергается каким-либо образом, поэтому ее нельзя модифицировать своими собственными методами (легко: построить частный экземпляр);
  • Все методы, которые читают, хеш-таблица также синхронизируется при использовании одновременно с методом записи. Представьте, что может произойти, если вызывается get(), когда карта хэша находится на полпути.

Последний пункт отстой, если вы должны читать с вашей карты хэша одновременно с записью в нее; использование ConcurrentHashMap в этом случае.

Если у вас есть только одна совместная запись в хэш-карту и , то прочитайте ее только в одном потоке, ваше решение должно быть в порядке.

+1

Нет, не только «если используется * в то же время *»; если чтения неправильно синхронизированы, состояние чтения может быть полностью неверным, независимо от того, временно ли они перекрывают записи. –

+0

@SoftwareMonkey: это звучит довольно удивительно. Не могли бы вы описать сценарий, когда два параллельных _reads_ с хэш-карты могут оказаться неверными, если карта не будет мутирована одновременно? (Для простоты рассмотрим карту неизменяемую.) – 9000

+0

См. Мой ответ для полного объяснения. –