Java ConcurrentHashMap не является потокобезопасным .. wth?

Раньше я использовал HashMap, например

   public Map<SocketChannel, UserProfile> clients = new HashMap<SocketChannel, UserProfile>();

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

который теперь выглядит так

public ConcurrentHashMap<SocketChannel, UserProfile> clients = new ConcurrentHashMap<SocketChannel, UserProfile>();

Мой дизайн сервера работает так. У меня есть рабочий поток(и) для обработки огромного количества пакетов. Каждый пакет проверяется с помощью подпрограммы packetHandler (не являющейся частью потока), практически любой клиент может вызвать ее в любое время, она почти статична, но это не так.

Весь мой сервер в основном однопоточный, за исключением части обработки пакетов.

В любом случае, когда кто-то использует команду, например, подсчитывает всех клиентов онлайн и получает от них некоторую информацию.

Также возможно, что клиенты могут отключаться и удаляться из ConcurrentHashMap во время подсчета (что вызывает мои проблемы).

Также я хотел бы добавить сюда код.

                int txtGirls=0;
                int vidGirls=0;
                int txtBoys=0;
                int vidBoys=0;
                Iterator i = clients.values().iterator();
                while (i.hasNext()) {
                    UserProfile person = (UserProfile)i.next();
                    if(person != null) {
                        if(person.getChatType()) {
                            if(person.getGender().equals("m"))
                                vidBoys++;
                            else //<-- crash occurs here.
                                vidGirls++;
                        } else if(!person.getChatType()) {
                            if(person.getGender().equals("m"))
                                txtBoys++;
                            else
                                txtGirls++;
                        }
                    }
                }

Я имею в виду, конечно, что я собираюсь исправить это, добавив исключение try-catch внутри Iterator, чтобы пропустить эти нулевые клиенты.

Но чего я не понимаю, если он проверяет выше, if(person != null) не должен ли вложенный код работать автоматически..

если это не означает, что он был удален во время итерации, что должно быть невозможно, поскольку он потокобезопасен, wtf?

Что я должен делать? или лучше всего попробовать Exception?

Вот исключение

java.lang.NullPointerException
    at Server.processPackets(Server.java:398)
    at PacketWorker.run(PacketWorker.java:43)
    at java.lang.Thread.run(Thread.java:636)

ProcessPackets содержит приведенный выше код. а в комментарии указано количество строк #

Спасибо, что просветили меня.


person SSpoke    schedule 16.09.2010    source источник
comment
Вы пробовали Collections.synchronizedMap(map) ?   -  person zengr    schedule 16.09.2010
comment
TF заключается в том, что ConcurrentHashMap является потокобезопасным, но помимо потокобезопасности вы ожидаете чего-то еще.   -  person Stephen C    schedule 16.09.2010
comment
также было бы полезно, если бы вы объяснили, что здесь означает сбой. Что за авария? Что за исключение?   -  person Stephen C    schedule 16.09.2010
comment
Пожалуйста, ответьте на вопросы @Stephen C и перепишите это как вопрос, а не как историю.   -  person Jacob Tomaw    schedule 16.09.2010
comment
Извините, я новичок во всей этой сделке, но я отредактировал вопрос и поставил ошибку исключения, но это все еще очень туманная трассировка стека. Что бы вы, ребята, порекомендовали вместо расширения класса что-то простое. Я бы сделал идею копирования коллекции, но я не уверен, что это вообще возможно быть на 100% безопасным, поскольку все объекты связаны ссылками, так что в любом случае это бессмысленно, если скопированный объект деконструируется/уничтожается где-то еще?   -  person SSpoke    schedule 16.09.2010
comment
JAVA DOC (docs.oracle.com/javase /6/docs/api/) говорит, что класс ConcurrentHashMap полностью совместим с Hashtable в программах, которые полагаются на его безопасность потоков, но не на его детали синхронизации.   -  person Kanagavelu Sugumar    schedule 25.10.2013


Ответы (4)


Вам необходимо прочитать javadocs для ConcurrentHashMap.values(), обратив особое внимание на это описание того, как работает итератор для коллекции values():

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

Итератор не дает вам непротиворечивого снимка состояния коллекции значений, но он является потокобезопасным, а ожидаемый диапазон поведения четко указан.

Если вам нужна реализация Map, которая дает вам непротиворечивый снимок значений (или ключей, или записей) в карте И позволяет выполнять итерацию одновременно с изменениями, вам, вероятно, потребуется создать собственный класс-оболочку Map (который копирует коллекции атомарно) ... или полномасштабная пользовательская реализация карты. Оба, вероятно, будут намного медленнее, чем ConcurrentHashMap для вашего варианта использования.

person Stephen C    schedule 16.09.2010
comment
Спасибо, кто-то должен был заглушить это для меня, поскольку я простой человек. Но я не понимаю, сохраняется ли коллекция значений во время итерации и не может содержать нуль, почему я получаю исключение нулевого указателя? тогда это проблема со ссылками, да ... так что эти ссылки связаны как адреса с одним и тем же классом. Что, кроме затрат на CocurrentHashMap? атомарно вы имеете в виду, как собственный метод копирования? неважно, я нашел это Пакет java.util.concurrent.atomic - person SSpoke; 16.09.2010
comment
Под атомарностью я подразумеваю единичное непрерывное действие; см. en.wikipedia.org/wiki/Atomicity_%28programming%29. Кстати, в Java нет такого понятия, как (атомарный) собственный метод копирования. Единственные способы гарантировать атомарность в Java — использовать примитивную или java.util.concurrent.* блокировку, или использовать volatile. Оба подхода имеют оговорки. - person Stephen C; 16.09.2010
comment
@SSpoke - если NPE произошло именно в указанном вами месте, это, вероятно, не из-за null, возвращаемого итератором. Скорее всего, person.getGender() вернул null. - person Stephen C; 16.09.2010
comment
Хорошо, это getGender(). Я упустил из виду, что пол не строится с классом, но он приходит через секунду из ответного пакета, поскольку это может быть задержка, которая вызвала нуль. - person SSpoke; 16.09.2010
comment
Я хотел бы задать последний вопрос. Мне кажется, теперь у меня сложилось впечатление, что я не могу модифицировать, например, с помощью put/remove? пока я повторяю ConcurrentHashMap? в любом месте? если это правда, он просто пропускает код? который использует put/remove или устанавливает блок? любой из них довольно плохой, но, надеюсь, он блокирует .. я бы настроил тесты, но, может быть, вы, ребята, сможете дать мне ответ быстрее - person SSpoke; 16.09.2010
comment
Ну я думаю ты забросил эту тему. Похоже, вы получили наибольшее количество голосов, я просто приму ваш ответ. - person SSpoke; 16.09.2010
comment
@SSpoke - (я не бросил эту тему, но в моей жизни случаются и другие вещи.) Вы можете изменять карту во время итерации. Однако вы можете или не можете наблюдать за результатами изменений в параллельной итерации. Это то, что говорит javadoc. - person Stephen C; 16.09.2010
comment
Хорошо, приятель, спасибо за все, что я действительно ценю. В итоге я использовал статический счетчик, который добавляет ++ к любому tboy/tgirl/vboy/vgirl, а затем при очистке соединения удаляется в зависимости от их пола/camType и т. д. после 24 часов работы счетчики полностью облажались, некоторые даже в минусах, ха-ха но ладно я разберусь - person SSpoke; 17.09.2010

java.util.concurrent.ConcurrentHashMap не допускает нулевое значение. Итак, нулевая проверка (человек != нуль) в вашем коде не нужна.

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

person heekyu    schedule 16.09.2010
comment
Спасибо за это, я удалю это (надеюсь, вы не говорите дезинформацию, извините за грубость). Но благодаря вам, ребята, я каждый день узнаю новый трюк! - person SSpoke; 16.09.2010
comment
@SSpoke JavaDoc ясно дает понять, что @heekyu не дезинформирует. download.oracle.com/javase/6/ документы/api/java/util/concurrent/ - person Jacob Tomaw; 16.09.2010

Я не вижу ничего плохого в вашем коде. Поскольку маловероятно, что сбой на самом деле происходит в else, вполне вероятно, что метод getGender() возвращает null.

person Steve Emmerson    schedule 16.09.2010
comment
Да, это так.. но это потому, что человек равен нулю.. кажется, я соглашусь с идеей копирования значений в коллекции? ИДК, если это решит это? - person SSpoke; 16.09.2010
comment
@SSpoke в вашем коде невозможно, чтобы person.getChatType() не разыменовывал null, а затем person.getGender() не разыменовывал null. Я думаю, вы неправильно интерпретируете свое исключение NullPointerException. - person Jacob Tomaw; 16.09.2010

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

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

Вот пара ссылок, которые могут быть полезны:

Здесь немного говорится о том, что улучшенный параллелизм связан с ослаблением некоторых промисов. http://www.ibm.com/developerworks/java/library/j-jtp07233.html

объяснение свойств согласованности памяти: http://download-llnw.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

person James Black    schedule 16.09.2010
comment
правда .. я трачу много циклов процессора, но переопределение CocurrentHashMap кажется слишком сложной задачей, поскольку это всего лишь одна команда .. и еще много других команд, которые выполняют аналогичные действия. - person SSpoke; 16.09.2010
comment
Привет, Джеймс, могу я быть уверен в одном на 100%? если класс UserProfile, который назначается каждому клиенту, копируется в новую коллекцию, которую я буду повторять, если CocurrentHashMap удалит этот UserProfile, который не является ссылкой (указателем?) То же самое? то есть оба будут удалены? Вы можете прояснить это для меня. - person SSpoke; 16.09.2010