Повреждение члена экземпляра Java

У меня есть многопоточное приложение Java 1.6 (5-7 потоков, большинство из них простаивает), которое ведет себя странно. Поток включает обновление устройства с 4-байтовым идентификатором.
Я храню идентификатор в частном массиве байтов. При успешном обновлении через ~ 4 секунды устройство отправляет сообщение STATUS, в котором я сравниваю его идентификатор с тем, который у меня есть, очищаю частный массив байтов и отключаю таймер ошибки. Вся работа выполняется в экземпляре класса singleton.

Странное поведение:
Я печатаю значение частного байтового массива из метода, который вызывается периодически. В течение 4 секунд ожидания сообщения STATUS в журнале отображается другой идентификатор (не мусор, а 4-байтовый идентификатор другого объекта). Проверка значения с помощью точки останова показывает это недопустимое значение (то есть это не ошибка журнала) .
Но, тем не менее, когда приходит сообщение STATUS, я сравниваю идентификатор с тем, который у меня есть, и они совпадают!

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

Вот псевдокод моего установщика / получателя и периодического статуса + тревожный журнал:

public class Manager {
    private volatile byte[] readerID = null;

    public synchronized void setReaderID(byte[] readerID) {
        this.readerID = readerID;
        logger.debug("readerID = {}", StringUtilities.binaryToAscii(this.readerID));
    }

    public synchronized byte[] getReaderID() {
        if (this.readerID == null)
            return null;
        return Arrays.copyOf(this.readerID, this.readerID.length);
    }

    /* Called every second */
    public void periodicStatus() {
        logger.debug("readerID = {}", StringUtilities.binaryToAscii(getReaderID()));
    }
}

13:53:46,103|ad-5|INFO |Manager|readerUpdateFinish(): Received firmware install finish for reader 000189D0 at slot 0
13:53:46,103|ad-5|DEBUG|Manager|setReaderID(): readerID = 000189D0
13:53:46,103|ad-5|DEBUG|Manager|readerUpdateFinish(): triggered reader firmware timer, 1526986426103, 000189D0
13:53:46,408|ad-5|DEBUG|Manager|periodicStatus(): readerID = E69EAD03 // <- where's the setter???
13:53:50,030|ad-5|INFO |Manager|readerStatus(): Received status information for reader 000189D0 at slot 0
13:53:50,031|ad-5|DEBUG|Manager|setReaderID(): readerID = null
13:53:50,031|ad-5|DEBUG|Manager|readerStatus(): timer cleared, null

Любые идеи?

Вам нужно будет показать соответствующий код.

Kayaman 22.05.2018 13:27

Вы должны добавить оставшийся исходный код, который использует этот исходный код таким образом, чтобы его могли протестировать все. См. stackoverflow.com/help/mcve. В зависимости от исходного кода, который мы не видим, возможно, вы измените массив после того, как присвоили ссылку частному полю.

Progman 22.05.2018 21:11

Добавление большего количества исходного кода проблематично; это довольно много кода, выполняемого из нескольких потоков, в зависимости от конфигурации. Я попытался предоставить минимальный синхронизированный сеттер, который регистрирует изменения, и получатель, который возвращает копию, но все же данные в журнале кажутся измененными. Скорее философская проблема Java (глазами программиста на языке C)

Rami Rosenbaum 23.05.2018 14:16

@RamiRosenbaum Массив, который вы назначили полю, известен в двух местах. Одно место - это поле Manager.readerId, другое - это место, где вы вызвали метод setReaderID(). Имейте в виду, что массивы - это объекты в java, поэтому ссылка в поле Manager.readerId такая же, как и ссылка, в которой вы вызываете метод setReaderID(). Поэтому, когда вы меняете массив «в одном месте», вы меняете массив «в другом месте», потому что на самом деле это тот же самый массив. Вот почему мы просим предоставить полную MCVE, потому что тогда мы сможем сказать вам, где находится ошибка.

Progman 23.05.2018 21:11

Progman - спасибо! Действительно, буфер readerID поступает с транспортного уровня связи и является членом-экземпляром. readerID никогда не копируется. Я попробую это и обновлю.

Rami Rosenbaum 25.05.2018 08:26

Progman - даже с помощью фрагментов кода вы заметили проблему. Я не являюсь родным Java-программистом и меня бросили на этот большой кусок старого кода. Вы заслуживаете похвалы.

Rami Rosenbaum 27.05.2018 11:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
6
137
1

Ответы 1

Как указал Прогман, readerID передается по ссылке. Он поступает с транспортного уровня, удерживается там как член-экземпляр и обновляется следующим входящим сообщением с новым отображаемым идентификатором.

Другие вопросы по теме