Возможная проблема параллелизма в разработке Xlet

Я занимаюсь разработкой Xlet с использованием Java 1.4 API.

Документы говорят Xlet методы интерфейса (на самом деле это методы жизненного цикла xlet) вызываются в его специальном потоке (а не в потоке EDT). Я проверил логи - это правда. Это немного удивительно для меня, потому что это отличается от сред BB/Android, где методы жизненного цикла вызываются в EDT, но пока все нормально.

В коде проекта я вижу, что приложение широко использует Display.getInstance().callSerially(Runnable task) звонки (это LWUIT способ запуска Runnable на тему EDT).

Таким образом, в основном некоторые фрагменты кода внутри класса реализации Xlet выполняют операции создания / обновления / чтения над внутренними объектами состояния xlet из потока EDT, а некоторые другие фрагменты кода делают из потока жизненного цикла без какой-либо синхронизации (в том числе переменные состояния не являются объявлен как изменчивый). Что-то вроде этого:

class MyXlet implements Xlet {

    Map state = new HashMap();

    public void initXlet(XletContext context) throws XletStateChangeException {
        state.put("foo", "bar"); // does not run on the EDT thread

        Display.getInstance().callSerially(new Runnable() {
            public void run() {
                // runs on the EDT thread
                Object foo = state.get("foo");
                // branch logic depending on the got foo
            }
        });
    }

    ..
}

Мой вопрос: создает ли это фон для редких проблем параллелизма? Должен ли доступ к состоянию синхронизироваться явно (или, по крайней мере, состояние должно быть объявлено как volatile)?

Я предполагаю, что это зависит от того, выполняется ли код на многоядерном процессоре или нет, потому что я знаю, что на многоядерном процессоре, если 2 потока работают на его собственном ядре, то переменные кэшируются, поэтому каждый поток имеет его собственная версия состояния, если явно не синхронизированы.

Я хотел бы получить достоверный ответ на мои вопросы.

1 ответ

Да, в описанном вами сценарии доступ к общему состоянию должен быть безопасным для потоков.

Есть 2 проблемы, о которых вам нужно знать:

Первая проблема, видимость (о которой вы уже упоминали), все еще может возникать в однопроцессорной системе. Проблема заключается в том, что JIT-компилятору разрешено кэшировать переменные в регистрах, и при переключении контекста ОС, скорее всего, будет выгружать содержимое регистров в контекст потока, чтобы впоследствии его можно было возобновить. Однако это не то же самое, что запись содержимого регистров обратно в поля объекта, поэтому после переключения контекста мы не можем предполагать, что поля объекта обновлены.

Например, возьмите следующий код:

class Example {
    private int i;

    public void doSomething() {
        for (i = 0; i < 1000000; i ++) {
            doSomeOperation(i);
        }
    }
}

Поскольку переменная цикла (поле экземпляра) i не объявлен как volatile, JIT разрешено оптимизировать переменную цикла i используя регистр процессора. Если это произойдет, то JIT не потребуется записывать значение регистра обратно в переменную экземпляра. i пока после завершения цикла.

Итак, допустим, что поток выполняет цикл, описанный выше, и затем получает приоритет. Новый запланированный поток не сможет увидеть последнее значение i потому что последняя ценность i находится в регистре, и этот регистр был сохранен в локальном контексте выполнения потока. Как минимум поле экземпляра i нужно будет объявить volatile заставить каждое обновление i быть видимым для других тем.

Вторая проблема - это согласованное состояние объекта. Возьмите HashMap в вашем коде, например, внутренне он состоит из нескольких не финальных переменных-членов size, table, threshold а также modCount, куда table это массив Entry это формирует связанный список. Когда элемент помещается в карту или удаляется с карты, две или более из этих переменных состояния должны обновляться атомарно, чтобы состояние было согласованным. За HashMap это должно быть сделано в течение synchronized блок или аналогичный, чтобы он был атомным.

Что касается второго вопроса, у вас все равно будут проблемы при работе на однопроцессорном компьютере. Это связано с тем, что ОС или JVM могут превентивно переключать потоки, в то время как текущий поток частично выполняет выполнение метода put или remove, а затем переключается на другой поток, который пытается выполнить какую-то другую операцию с тем же HashMap,

Представьте, что произойдет, если ваш поток EDT был в середине вызова метода 'get', когда происходит преимущественное переключение потока, и вы получаете обратный вызов, который пытается вставить другую запись в карту. Но на этот раз карта превышает коэффициент загрузки, что приводит к изменению размера карты и повторному хешированию и вставке всех записей.

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