Освоение видимости объектов в Java

Освоение видимости объектов в Java

27 февраля 2024 г.

Рассмотрим следующий многопоточный код:

public class NoVisibility {
    private static boolean ready;
    private static int number;
    private static int ITERATION = 1000000;
    private static class ReaderThread extends Thread {
        public void run() {
            while(!ready) {
                Thread.yield();
            }
            if(number != 56) System.out.println("Bug");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for(int i=1; i<=ITERATION; ++i){
            if(i%(ITERATION/10)==0) System.out.println("Iteration: " + i);
            number = 0;
            ready = false;
            final Thread thread = new ReaderThread();
            thread.start();
            number = 56;
            ready = true;
            thread.join();
        }
    }
}

Вопрос ко всем читателям — «Выполняя основной метод NoVisibility, увидим ли мы когда-нибудь надпись «Bug» на терминале?».

Любопытный ответ: ДА!

Вот результат, который я получил при запуске этой программы —

Iteration: 100000
Iteration: 200000
Iteration: 300000
Bug
Iteration: 400000
Bug
Bug
Iteration: 500000
Iteration: 600000
Iteration: 700000
Iteration: 800000
Iteration: 900000
Iteration: 1000000

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

Видимость объекта

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

В многопоточной среде записи, выполняемые одним потоком в общие объекты, могут быть не сразу видны другим потокам. Хуже того, эти записи могут вообще не стать видимыми! Еще более странно то, что видимость в многопоточной среде — это не все или ничего. Другие потоки могут видеть обновленное состояние некоторых переменных и, с другой стороны, устаревшее значение других переменных.

Чтобы преодолеть эти проблемы, необходимо использовать правильную синхронизацию каждый раз при доступе к общим объектам.

Что пошло не так с NoVisibility?

Наш класс NoVisibility страдает теми же проблемами с видимостью. Когда основной поток записывает в общие объекты number и ready, эти обновления могут быть не сразу видны потоку чтения.

Это может привести к таким проблемам, как —

  • Вечное зацикливание потока чтения
  • Изменение порядка приводит к тому, что изменения, внесенные в готово, становятся видимыми до того, как изменения, внесенные в number, становятся видимыми

Отсутствие правильной синхронизации внутри класса NoVisibility приводит к этим состояниям гонки.

Синхронизация для обеспечения видимости

Мы обсуждали синхронизированные блоки Java в эта статья как механизм обеспечения атомарных операций. Блоки синхронизированные служат еще одной цели — «Видимость объекта». Внутреннюю блокировку можно использовать, чтобы гарантировать, что один поток предсказуемо видит эффекты другого!

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

Locking for Visibility. Guarded using the same lock ‘this’.

<блок-цитата>

Используя блокировки, мы обеспечиваем видимость памяти. Поэтому важно, чтобы как поток чтения, так и поток записи поддерживали правильные блокировки!

Изменчивые переменные

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

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

<блок-цитата>

Запись в изменчивую переменную можно сравнить с выходом из синхронизированного блока, а чтение изменчивой переменной можно сравнить с входом в синхронизированный блок, защищенный одной и той же блокировкой.

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


Часто то, что интуитивно кажется правильным в однопоточной среде, может пойти совсем не так в многопоточной среде. Видимость объектов — одна из таких вещей. Они настолько нелогичны, что, если вы о них не знаете, трудно обнаружить, что из-за них возникают состояния гонки.

Надеюсь, вам понравилось читать этот блог! Следите за новостями!

:::информация Также опубликовано здесь.

:::


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE