Всегда ли объект видит свое последнее внутреннее состояние независимо от потока? - PullRequest
12 голосов
/ 05 апреля 2019

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

class Counter implements Runnable {
    private int count = 0;

    @Override
    public void run() {
      count++;
    }
}

Counter counter = new Counter();
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.scheduleWithFixedDelay(counter, 1, 1, TimeUnit.SECONDS);

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

Ответы [ 3 ]

11 голосов
/ 05 апреля 2019

Всегда ли объект видит свое последнее внутреннее состояние независимо от потока?

Просто чтобы прояснить для целей этого вопроса и его ответов, объект не делает ничего; это просто память. Потоки являются исполняющей сущностью. Неверно говорить, видит ли объект все, что угодно . Это поток, который видит / читает состояния объекта.

Это не указано в javadoc, но

Executors.newScheduledThreadPool(5);

возвращает ScheduledThreadPoolExecutor.

Ваш код использует

executorService.scheduleWithFixedDelay(counter, 1, 1, TimeUnit.SECONDS);

Javadoc для ScheduledThreadPoolExecutor#scheduledWithFixedDelay состояний

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

Класс Javadoc дальнейшие уточнения

Последовательное выполнение периодического задания, запланированного через scheduleAtFixedRate или scheduleWithFixedDelay не перекрываются. Хотя разные потоки могут выполнять разные исполнения, последствия предыдущих казней происходят раньше, чем последующие .

Таким образом, каждое выполнение Counter#run гарантированно будет видеть значение count после того, как оно было увеличено предыдущим выполнением. Например, третье выполнение будет считывать значение count, равное 2, прежде чем оно выполнит свое увеличение.

Вам не нужен volatile или какой-либо другой дополнительный механизм синхронизации для этого конкретного варианта использования .

7 голосов
/ 05 апреля 2019

Нет, этот код не является поточно-ориентированным, поскольку не существует отношения до-100 * * между приращениями, сделанными в разных потоках, начинающихся с ScheduledExecutorService.

Чтобы исправить это, вам нужно либо пометить переменную как volatile, либо переключиться на AtomicInteger или AtomicLong.

UPDATE:

Как упомянуто @BoristheSpider, в общем случае при увеличении / уменьшении создание переменной volatile недостаточно, поскольку приращение / уменьшение не является атомарным, и одновременный вызов из нескольких потоков вызовет состояние гонки и пропущенные обновления. Тем не менее, в этом конкретном случае scheduleWithFixedDelay() гарантирует (согласно Javadoc), что выполнение запланированных задач будет перекрываться, поэтому volatile также будет работать , в данном конкретном случае даже с приращением.

2 голосов
/ 05 апреля 2019

Нет, этот код не является потокобезопасным, поскольку не происходит до связи между различными потоками, обращающимися к count.

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