Каковы основные применения yield () и чем он отличается от join () и interrupt ()? - PullRequest
96 голосов
/ 08 августа 2011

Я немного озадачен использованием метода yield() в Java, особенно в приведенном ниже примере кода. Я также читал, что yield () «используется для предотвращения выполнения потока».

Мои вопросы:

  1. Я полагаю, что приведенный ниже код приводит к одному и тому же выводу как при использовании yield(), так и когда он не используется. Это правильно?

  2. Каковы, по сути, основные области применения yield()?

  3. Чем yield() отличается от методов join() и interrupt()?

Пример кода:

public class MyRunnable implements Runnable {

   public static void main(String[] args) {
      Thread t = new Thread(new MyRunnable());
      t.start();

      for(int i=0; i<5; i++) {
          System.out.println("Inside main");
      }
   }

   public void run() {
      for(int i=0; i<5; i++) {
          System.out.println("Inside run");
          Thread.yield();
      }
   }
}

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

Inside main
Inside main
Inside main
Inside main
Inside main
Inside run
Inside run
Inside run
Inside run
Inside run

Ответы [ 9 ]

91 голосов
/ 08 августа 2011

Источник: http://www.javamex.com/tutorials/threads/yield.shtml

Windows

В реализации Hotspot работает Thread.yield() изменилось между Java 5 и Java 6.

В Java 5 Thread.yield() вызывает вызов Windows API Sleep(0). это имеет специальный эффект очистки текущего потока и поместить его в конец очереди для уровня приоритета . В других слова, все работающие потоки с одинаковым приоритетом (и те, приоритет) получит шанс на запуск до следующего потока учитывая время процессора. Когда это в конечном счете перенесено, оно вернется с полным полным квантом , но не «переносит» ни один из оставшийся квант от времени уступки. Такое поведение немного отличается от ненулевого сна, где спит нить как правило, теряет 1 квантовое значение (фактически, 1/3 от тика 10 или 15 мс).

В Java 6 это поведение было изменено. Hotspot VM теперь реализует Thread.yield() с использованием Windows SwitchToThread() API-вызова. Этот звонок заставляет текущий поток отказаться от его текущего временного интервала , но не его весь квант. Это означает, что в зависимости от приоритетов других потоки, выходной поток может быть запланирован назад в одном прерывании период спустя . (Подробнее см. Раздел планирование потоков информация о временных срезах.)

Linux

В Linux Hotspot просто вызывает sched_yield(). Последствия этот вызов немного другой, и, возможно, более серьезный, чем под Окна:

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

(Подробнее о приоритетах см. В разделе планирование потоков и планирование алгоритмов.)

Когда использовать yield()?

Я бы сказал практически никогда . Его поведение не определено стандартно и, как правило, есть лучшие способы выполнения задач, которые вы возможно, захотите выполнить с yield ():

  • если вы пытаетесь использовать только часть ЦП , вы можете сделать это более управляемым способом, оценив объем ЦП в потоке использовал в своем последнем фрагменте обработки, затем sleep для некоторых количество времени для компенсации: см. метод sleep () ;
  • если вы ожидаете, пока процесс или ресурс завершат или станут доступными, есть более эффективные способы сделать это, например, используя join () для ожидания завершения другого потока, используя механизм wait / notify , позволяющий одному потоку передавать сигналы другому что задача завершена, или в идеале с помощью одного из Java 5 конструкции параллелизма, такие как семафор или очередь блокировки .
36 голосов
/ 28 июля 2014

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

Как мы знаем, yield заставляет вызывающий поток отказаться от процессора, на котором он работает, чтобы можно было запланировать запуск другого потока. Это полезно, когда текущий поток на данный момент завершил свою работу, но хочет быстро вернуться в начало очереди и проверить, изменилось ли какое-либо условие. Чем это отличается от условной переменной? yield позволяет потоку намного быстрее вернуться в рабочее состояние. При ожидании переменной условия поток приостанавливается, и ему нужно дождаться, пока другой поток покажет, что он должен продолжить. yield в основном говорит: «разрешить запуск другого потока, но позвольте мне вернуться к работе очень скоро, так как я ожидаю, что что-то очень быстро изменится в моем состоянии». Это намекает на занятое вращение, когда условие может быстро измениться, но приостановка потока приведет к значительному снижению производительности.

Но достаточно болтовни, вот конкретный пример: параллельный паттерн волнового фронта. Базовым примером этой проблемы является вычисление отдельных «островков» из 1 в двумерном массиве, заполненном 0 и 1. «Остров» - это группа ячеек, смежных друг с другом по вертикали или по горизонтали:

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 1 1

Здесь у нас есть два острова 1: верхний левый и правый нижний.

Простое решение состоит в том, чтобы сделать первый проход по всему массиву и заменить 1 значения счетчиком с приращением таким образом, чтобы к концу каждая 1 заменялась своим порядковым номером в главном порядке строки:

1 0 0 0
2 3 0 0
0 0 0 4
0 0 5 6
0 0 7 8

На следующем шаге каждое значение заменяется минимумом между собой и значениями его соседей:

1 0 0 0
1 1 0 0
0 0 0 4
0 0 4 4
0 0 4 4

Теперь мы можем легко определить, что у нас есть два острова.

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

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

Как видите, yield - это довольно тонкая оптимизация. Использование в неправильном месте, например ожидание состояния, которое редко изменяется, приведет к чрезмерному использованию ЦП.

Извините за долгую болтовню, надеюсь, я все прояснил.

12 голосов
/ 04 апреля 2013

О разнице между yield(), interrupt() и join() - в общем, не только в Java:

  1. уступая : буквально, чтобы "уступить"значит отпустить, сдаться, сдаться.Выходящий поток сообщает операционной системе (или виртуальной машине, или что-то еще), что он готов позволить другим потокам планироваться вместо нее.Это указывает на то, что он не делает что-то слишком критическое.Это всего лишь подсказка, и не гарантированно будет иметь какой-либо эффект.
  2. присоединение : когда несколько потоков «соединяются» на каком-то дескрипторе, токене или объекте, все они ждут, покавсе другие соответствующие потоки завершили выполнение (полностью или до своего собственного соответствующего соединения).Это означает, что группа потоков все выполнила свои задачи.Затем каждый из этих потоков может быть запланирован для продолжения другой работы, имея возможность предположить, что все эти задачи действительно выполнены.(Не путать с SQL-соединениями!)
  3. прерывание : используется одним потоком для "высовывания" другого потока, который находится в спящем режиме, ожидает или присоединяется - так, чтобы он был запланирован дляпродолжайте бежать снова, возможно, с указанием, что оно было прервано.(Не путать с аппаратными прерываниями!)

Подробнее о Java см.

  1. Присоединение:

    Как использоватьThread.join? (здесь, в StackOverflow)

    Когда присоединяться к потокам?

  2. Выход:

  3. Прерывание:

    Является ли Thread.interrupt () злым? (здесь, в StackOverflow)

9 голосов
/ 08 августа 2011

Во-первых, фактическое описание:

Заставляет временно выполняющийся объект потока временно приостанавливать и разрешать выполнение другим потокам.

Теперь очень вероятно, чтоВаш основной поток выполнит цикл пять раз, прежде чем будет выполнен метод run нового потока, поэтому все вызовы yield будут происходить только после выполнения цикла в основном потоке.

join остановит текущий поток, пока поток, вызываемый с помощью join(), не будет выполнен.

interrupt прервет поток, к которому он вызывается, вызывая InterruptedException .

yield разрешает переключение контекста на другие потоки, поэтому этот поток не будет использовать всю загрузку ЦП процессом.

3 голосов
/ 13 января 2018

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

Нет никакой практической разницы Thread.yield() между версиями Java с 6 по 9.

TL; DR;

Выводы на основе исходного кода OpenJDK (http://hg.openjdk.java.net/).

Если не принимать во внимание поддержку HotSpot зондов USDT (информация о трассировке системы описана в руководство по трассировке ) и свойство JVM ConvertYieldToSleep, то исходный код yield() почти одинаков. См. Объяснение ниже.

Java 9 :

Thread.yield() вызывает специфичный для ОС метод os::naked_yield():
В Linux:

void os::naked_yield() {
    sched_yield();
}

В Windows:

void os::naked_yield() {
    SwitchToThread();
}

Java 8 и более ранние версии:

Thread.yield() вызывает специфичный для ОС метод os::yield():
В Linux:

void os::yield() {
    sched_yield();
}

В Windows:

void os::yield() {  os::NakedYield(); }

Как видите, Thread.yeald() в Linux одинаков для всех версий Java.
Давайте посмотрим на Windows os::NakedYield() из JDK 8:

os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    if (os::Kernel32Dll::SwitchToThreadAvailable()) {
        return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep(0);
    }
    return os::YIELD_UNKNOWN ;
}

Разница между Java 9 и Java 8 в дополнительной проверке существования метода SwitchToThread() API Win32. Тот же код присутствует для Java 6.
Исходный код os::NakedYield() в JDK 7 немного отличается, но имеет такое же поведение:

    os::YieldResult os::NakedYield() {
    // Use either SwitchToThread() or Sleep(0)
    // Consider passing back the return value from SwitchToThread().
    // We use GetProcAddress() as ancient Win9X versions of windows doen't support SwitchToThread.
    // In that case we revert to Sleep(0).
    static volatile STTSignature stt = (STTSignature) 1 ;

    if (stt == ((STTSignature) 1)) {
        stt = (STTSignature) ::GetProcAddress (LoadLibrary ("Kernel32.dll"), "SwitchToThread") ;
        // It's OK if threads race during initialization as the operation above is idempotent.
    }
    if (stt != NULL) {
        return (*stt)() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
    } else {
        Sleep (0) ;
    }
    return os::YIELD_UNKNOWN ;
}

Дополнительная проверка была отброшена из-за метода SwitchToThread(), доступного начиная с Windows XP и Windows Server 2003 (см. msdn notes ).

2 голосов
/ 03 августа 2014

Каковы, по сути, основные применения yield ()?

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

Я полагаю, что приведенный ниже код приводит к одинаковому результату как при использовании yield (), так и когда не используетсяЭто.Это правильно?

НЕТ, оба будут давать разные результаты.Без yield (), как только поток получит контроль, он выполнит цикл «Inside run» за один раз.Однако с помощью yield (), когда поток получает управление, он один раз напечатает «Внутренний запуск», а затем передаст управление другому потоку, если таковой имеется.Если нет ожидающих потоков, этот поток будет возобновлен снова.Таким образом, каждый раз, когда выполняется «Внутренний запуск», он будет искать другие потоки для выполнения, и, если поток недоступен, текущий поток будет продолжать выполняться.

Чем отличается yield () отметоды join () и interrupt ()?

yield () предназначены для предоставления места другим важным потокам, метод join () предназначен для ожидания завершения выполнения другим потоком, а interrupt () -для прерывания выполняющегося в данный момент потока, чтобы сделать что-то еще.

0 голосов
/ 05 мая 2017

Thread.yield() приводит к тому, что поток переходит из состояния «Работает» в состояние «Работает».Примечание: поток не переходит в состояние ожидания.

0 голосов
/ 02 августа 2014

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

все эти различия методов заключаются в том, что yield () переводит поток в режим удержания при выполнении другого потока и возвращении обратно после завершения этого потока; метод join () объединяет начало потоков, выполняя их до конца, а другой поток запускается после этот поток закончился, interrupt () остановит выполнение потока на некоторое время.

0 голосов
/ 12 мая 2014

Thread.yield ()

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

join ()

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

...