Странное поведение ConcurrentHashMap и SocketOutputStream

249
25 апреля 2017, 06:16

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

Программа делала следующее:

  1. Через сервлет обновляла значение у объекта Водитель.
  2. Через сокет объект возвращала результат клиенту.

Для этого был один метод (синхронизированный блок, чтобы сразу все пользователи не меняли значение водителю, потом переделаю; код с БД вырезал):

public synchronized void setDriverLimit(int driverId, double driverLimit) {
        mDriversMap.get(driverId).setMaxMoneyPerDay(new BigDecimal(driverLimit));
        mDriversBroadcast.onDriverAction(ActionWrapper.builder()
                .action(ActionWrapper.Action.CHANGED)
                .object(mDriversMap.get(driverId))
                .build());
}

После его вызова в сокет сервера записывался объект с новым значением (лимитом), за это отвечал метод onDriverAction, а на выходе у клиента почему-то появлялся объект, который был ДО вызова метода (который был в Map до того, как установился новый лимит). 6 часов ломал голову над проблемой, расставил везде логи - все работает правильно, но значения приходят не те... В сокет записывается объект с значением, скажем, 10, а приходит - 5! P.S. Я юыл очень удивлен и даже полез искать, нет ли такого глюка у OpenJDK, хотя и знал, что косяк мой... :))

В конце концов, решил посмотреть на аналогичные методы в том же классе и понял, что этот - единственный, который использует объекты из Map, а не новосозданные. Написал так:

public synchronized void setDriverLimit(int driverId, double driverLimit) throws SQLException {
        Driver driverWithNewLimit = Driver.builder()
                .autogeneratedIdNotUse(mDriversMap.get(driverId).getAutogeneratedIdNotUse())
                .driverId(driverId)
                .driverName(mDriversMap.get(driverId).getDriverName())
                .carNumber(mDriversMap.get(driverId).getCarNumber())
                .driverPhoneNumber(mDriversMap.get(driverId).getCarNumber())
                .driverPassword(mDriversMap.get(driverId).getDriverPassword())
                .maxMoneyPerDay(new BigDecimal(driverLimit))
                .lastToday(new BigDecimal(mDriversMap.get(driverId).getLastToday().doubleValue()))
                .build();
        mDriversMap.replace(driverId, driverWithNewLimit);
        mDriversBroadcast.onDriverAction(ActionWrapper.builder()
                .action(ActionWrapper.Action.CHANGED)
                .object(driverWithNewLimit)
                .build());
    }

На удивление все заработало! Смысл написания такого метода заключался в избежании всевозможных ссылок на старые объекты.

Забыл упомянуть, что в сокет все записывалось самым простым образом: брался OutputStream, преобразовывался в ObjectOutputStream и туда записывались массивы ActionWrapper методом writeObject(). Массивы, а не одиночные объекты, потому что они собирались каждые 5 секунд и отправлялись группой.

Вопрос: а из-за чего такое поведение? Где это потокобезопасная HashMap умудряется хранить ссылки или чего сокет обращается к старым ссылкам? Или, может, проблема в BigDecimal? В общем, надеюсь, что человек, который в этом разбирается сможет расписать в чем же дело :).

READ ALSO
threads размер папки

threads размер папки

нужна программа которая считает размер папки и напечатать результат по 1 секунду

184
Как получить координаты границы `Layout` в пикселях (px)

Как получить координаты границы `Layout` в пикселях (px)

Двигаю ImageView с помощью ImageViewanimate()

220
Как прочитать фото файл для загрузки в Bitmap?

Как прочитать фото файл для загрузки в Bitmap?

Пытаюсь загрузить изображение в Bitmap таким способом

183
Как и с помощью чего отследить движение?

Как и с помощью чего отследить движение?

Ребята Знаю вопросы такого формата не очень тут )Но всеже решилсяНужно спроектировать приложение котьорое на камере отлавливает движение...

214