Эти три потока не меняются при использовании Thread.yield ()?

Пытаясь попрактиковаться в моей ржавой Java, я хотел попробовать простой пример многопоточных общих данных и наткнулся на кое-что, что меня удивило.

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

главный

AtomicInteger counter = new AtomicInteger(0);

CounterThread ct1 = new CounterThread(counter, "A");
CounterThread ct2 = new CounterThread(counter, "B");
CounterThread ct3 = new CounterThread(counter, "C");

ct1.start();
ct2.start();
ct3.start();

CounterThread

public class CounterThread extends Thread
{   
    private AtomicInteger _count;
    private String _id;

    public CounterThread(AtomicInteger count, String id)
    {
        _count = count;
        _id = id;
    }

    public void run()
    {
        while(_count.get() < 1000)
        {
            System.out.println(_id + ": " + _count.incrementAndGet());
            Thread.yield();
        }
    }
}

Я ожидал, что когда каждый поток выполнит Thread.yield(), он передаст выполнение другому потоку, чтобы увеличить _count следующим образом:

A: 1
B: 2
C: 3
A: 4
...

Вместо этого я получил результат, в котором A увеличил бы _count в 100 раз, а затем передал его B. Иногда все три потока последовательно сменяют друг друга, но иногда один поток доминирует в течение нескольких шагов.

Почему Thread.yield() не всегда передает обработку другому потоку?


person Cory Klein    schedule 11.09.2013    source источник
comment
yield() - это предложение.   -  person Sotirios Delimanolis    schedule 11.09.2013
comment
Потоки так не работают. Они работают независимо, поэтому вы увидите всплески A: 1 A: 2 .... thread.yield () в лучшем случае является подсказкой.   -  person Gray    schedule 11.09.2013
comment
См. Мой ответ здесь: stackoverflow.com/questions/17816047 /   -  person Gray    schedule 11.09.2013
comment
@Gray Я не пытаюсь спроектировать что-то, где порядок выполнения Thread гарантирован, я просто был сбит с толку, когда Thread вел себя не так, как documentation Заставляет текущий выполняющийся объект потока временно приостановить и разрешить выполнение других потоков.   -  person Cory Klein    schedule 11.09.2013
comment
@CoryKlein Семантический трюк заключается в том, что другие потоки включают текущий поток. Или что разрешить не означает гарантировать.   -  person millimoose    schedule 11.09.2013
comment
Вы, вероятно, обнаружите, что вызовы yield полностью игнорируются и что поток фактически приостанавливается, когда один из его выходных буферов заполняется.   -  person Philip Sheard    schedule 11.09.2013
comment
Не игнорируйте начальную ветку! ct1 может запускаться несколько раз до завершения ct2.start () ...   -  person Arne Burmeister    schedule 11.09.2013
comment
Я переместил свои комментарии в ответ @Cory.   -  person Gray    schedule 11.09.2013


Ответы (2)


Я ожидал, что когда каждый поток выполнит Thread.yield (), он передаст выполнение другому потоку, чтобы увеличить _count следующим образом:

В многопоточных приложениях, которые вращаются, предсказать результат чрезвычайно сложно. Чтобы получить идеальный результат типа A:1 B:2 C:3 ..., вам придется проделать большую работу с блокировками и прочим.

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

Вместо этого я получил результат, в котором A увеличивал _count в 100 раз, а затем передавал его B. Иногда все три потока работали последовательно, но иногда один поток мог доминировать на нескольких приращениях.

Правильно, в целом с этими вращающимися циклами вы видите всплески вывода из одного потока, когда он получает временные интервалы. Это также сбивает с толку тот факт, что System.out.println(...) равно synchronized, что также влияет на синхронизацию. Если бы он не выполнял синхронизированную операцию, вы бы увидели еще более прерывистый вывод.

Почему Thread.yield () не всегда передает обработку другому потоку?

Очень редко использую Thread.yield(). В лучшем случае это подсказка для планировщика и, вероятно, игнорируется на некоторых архитектурах. Идея о том, что он «приостанавливает» цепочку, вводит в заблуждение. Это может привести к тому, что поток будет возвращен в конец очереди выполнения, но нет гарантии, что есть какие-либо потоки, ожидающие, поэтому он может продолжать работать, как если бы yield был удален.

См. Мой ответ здесь для получения дополнительной информации: нежелательный вывод в многопоточности

person Gray    schedule 11.09.2013
comment
В сторону: простой способ получить хорошо упорядоченный вывод - это использовать что-то вроде архитектуры очереди задач. Т.е. принудительное упорядочивание с помощью конструкции, предназначенной для этого явно, вместо того, чтобы пытаться сделать это косвенно. - person millimoose; 11.09.2013

Давайте прочитаем немного javadoc , не так ли?

Подсказка планировщику, что текущий поток готов предоставить свое текущее использование процессора. Планировщик может проигнорировать эту подсказку.

[...]

Этот метод редко бывает уместным. Это может быть полезно для целей отладки или тестирования, где может помочь воспроизвести ошибки из-за состояния гонки. Это также может быть полезно при разработке конструкций управления параллелизмом, например, в пакете java.util.concurrent.locks.

Вы не можете гарантировать, что другой поток получит процессор после yield(). Это зависит от планировщика, и, похоже, он / она не хочет этого в вашем случае. Вместо этого вы можете подумать о sleep()ing для тестирования.

person Sotirios Delimanolis    schedule 11.09.2013
comment
Бьюсь об заклад, тогда функциональность была такой же. Эта документация кажется неоднозначной в отношении этого вопроса. - person Cory Klein; 11.09.2013
comment
@SotiriosDelimanolis Документы могут быть пережитком того времени, когда Java использовала зеленые потоки - возможно, они не обновлялись полностью позже. - person millimoose; 11.09.2013