Может ли планировщик приостановить один поток и выполнить другой поток/работу?

Пусть у нас есть следующий код (мы будем запускать его на одноядерном процессоре):

Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable_1_1");
        System.out.println("runnable_1_2");
    }
};
Runnable runnable2 = new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable
runnable_1_1
runnable_2_1
runnable_2_2
runnable_1_2
1"); System.out.println("runnable
runnable_1_1
runnable_2_1
runnable_2_2
runnable_1_2
2"); } }; ExecutorService executorService = Executors.newSingleThreadExecutor(); // or Executors.newCachedThreadExecutor(); executorService.execute(runnable1); executorService.execute(runnable2); executorService.shutdown();

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

runnable_1_1
runnable_2_1
runnable_2_2
runnable_1_2

P.S.

Однопоточный исполнитель не является обязательным для этого примера. Обязательно, чтобы у нас было только одно ядро ​​ЦП


person gstackoverflow    schedule 01.03.2017    source источник


Ответы (3)


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

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

В результате операционная система может на короткое время приостановить работу одного потока, позволяя запустить другой поток или какой-либо другой процесс за пределами виртуальной машины Java. Общий ответ, скорее всего, да.

person Audrius Meskauskas    schedule 01.03.2017
comment
@bashnesnos почему? - person gstackoverflow; 01.03.2017
comment
@gstackoverflow Я позволю другим ребятам ответить первыми - person bashnesnos; 01.03.2017
comment
потому что нам нужен >1 поток, чтобы приостановить один поток на короткое время, позволив вместо него запуститься другому. - person Alexei Kaigorodov; 01.03.2017
comment
Также один поток может быть приостановлен в какой-то момент, так как существует много других системных процессов вне виртуальной машины Java. - person Audrius Meskauskas; 01.03.2017

Количество ЦП не имеет значения, когда вы пытаетесь рассуждать о коде на этом уровне. Теоретически вы можете запустить JVM в ОС, которая вызывает переключение контекста после каждой отдельной инструкции программы. Это было бы безумием, и ни одна ОС этого не делает, но как вы узнаете, просто взглянув на код Java?

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

Чтобы найти причину, нам нужно взглянуть на главу 17 JLS:

Два действия могут быть упорядочены отношением «происходит до». Если одно действие происходит раньше другого, то первое видно и упорядочено раньше второго.

Если у нас есть два действия x и y, мы пишем hb(x, y), чтобы указать, что x происходит раньше, чем y.

Если x и y являются действиями одного и того же потока и x предшествует y в программном порядке, то hb(x, y).

Существует край «происходит до» от конца конструктора объекта до начала финализатора (§12.6) для этого объекта.

Если действие x синхронизируется с последующим действием y, то мы также имеем hb(x, y).

Если hb(x, y) и hb(y, z), то hb(x, z).

Методы ожидания класса Object (§17.2.1) имеют связанные с ними действия блокировки и разблокировки; их отношения происходят до того, как они определяются этими связанными действиями.

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

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

С несколькими потоками можно только догадываться. Есть только две вещи, которые гарантированы:

  1. Первое сообщение из потока будет напечатано перед вторым. (см. выше)
  2. И каждая строка вывода будет содержать полное сообщение из одного из потоков, т.е. вы никогда не увидите строку перемешанного вывода. Это просто потому, что PrintStream.println() синхронизировано.

Итак, это теория.

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

person biziclop    schedule 01.03.2017

[[ Ответ @biziclop правильный, хотя и длинный и запутанный. ]]

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

Не в коде, который вы разместили. Вы отправляете 2 задания одному потоковому исполнителю:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Это означает, что только 1 поток будет выполнять ваши 2 Runnables. Когда этот 1-й поток блокируется, другой Runnable не выполняется, поэтому вывод не будет чередоваться. 1-й Runnable должен быть завершен до выполнения 2-го Runnable.

Если вы используете Executors.newCachedThreadExecutor();, то 2 Runnable могут выполняться одновременно, и их вывод может чередоваться. В этом случае первый Runnable может распечатать runnable_1_1, а затем сократить время, чтобы другой поток мог выполнить и отобразить его runnable_2_1 и т. д. Однако это состояние гонки и может быть маловероятным, но это возможно.

Однопоточный исполнитель не является обязательным для этого примера. Обязательно, чтобы у нас было только одно ядро ​​ЦП

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

person Gray    schedule 10.03.2017