Каковы основные применения yield () и чем он отличается от join () и interrupt ()?


106

Я немного смущен использованием 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

Этот вопрос следует закрыть, так как он слишком широкий .
Raedwald

Нет, он не возвращает тот же результат, когда у вас есть yield()и нет. когда у вас больше i, чем 5, вы можете увидеть эффект yield()метода.
lakshman 03

Ответы:


97

Источник: 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 теперь Thread.yield()использует SwitchToThread()вызов Windows API. Этот вызов заставляет текущий поток отказаться от своего текущего кванта времени , но не полностью. Это означает, что в зависимости от приоритетов других потоков, дающий поток может быть запланирован на один период прерывания позже . (См. Раздел о планировании потоков для получения дополнительной информации о временных интервалах.)

Linux

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

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

(См. Раздел о планировании потоков для получения более подробной информации о приоритетах и ​​алгоритмах планирования.)

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

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

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

18
«оставшийся квант», «весь квант» - где-то по пути кто-то забыл, что означает слово «квант»
kbolino

@kbolino Quantum - новый атом.
Евгений Сергеев

2
@kbolino - ... латынь : «сколько», «сколько» . Я не понимаю, насколько это противоречит приведенному выше использованию. Это слово просто означает описанное количество чего-либо, поэтому разделение его на использованные и оставшиеся части кажется мне вполне разумным.
Periata Breatta

@PeriataBreatta Думаю, это имеет больше смысла, если вы знакомы с этим словом за пределами физики. Я знал только физическое определение.
kbolino 03

Я назначил награду за этот вопрос, чтобы обновить этот ответ для 7, 8, 9. Измените его, указав текущую информацию о 7, 8 и 8, и вы получите награду.

40

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

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

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

1 0 0 0
1 1 0 0
0 0 0 1
0 0 1 1
0 0 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)которое более или менее С эквивалентом yield.

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

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

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


1
IIUC то, что вы представляете в документе, идея состоит в том, что в этом случае более эффективно использовать ожидание занятости, вызывающее, yieldкогда условие не выполняется, чтобы дать другим потокам возможность продолжить вычисление, а не использовать более высокий примитивы синхронизации уровней, верно?
Петр Пудлак

3
@ Петр Пудлак: Да. Я сравнил это с использованием потоковой сигнализации, и в этом случае разница в производительности была огромной. Поскольку условие может стать истинным очень быстро (это ключевая проблема), переменные условия работают слишком медленно, поскольку поток приостанавливается ОС, вместо того, чтобы отказываться от ЦП на очень короткое время yield.
Tudor

@Tudor отличное объяснение!
Разработчик Мариус Жиленас 07

1
«Обратите внимание на использование sleep (0), которое является более или менее эквивалентом yield для C.» .. ну, если вы хотите sleep (0) с java, почему бы вам просто не использовать это? Thread.sleep () уже существует. Я не уверен, что этот ответ объясняет, почему можно использовать Thread.yield () вместо Thread.sleep (0); Существует также существующая ветка, объясняющая, почему они разные.
eis

@eis: Thread.sleep (0) vs Thread.yield () выходит за рамки этого ответа. Я упоминал Thread.sleep (0) только для людей, которые ищут близкий эквивалент в C. Вопрос касался использования Thread.yield ().
Tudor

12

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

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

В частности, для Java см.

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

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

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

  2. Урожайность:

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

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


Что вы подразумеваете под присоединением дескриптора или токена? Методы wait () и notify () находятся в Object, что позволяет пользователю ждать любого произвольного объекта. Но join () кажется менее абстрактным и его нужно вызывать в конкретном потоке, который вы хотите завершить, прежде чем продолжить ... не так ли?
spaaarky21 06

@ spaaarky21: Я имел в виду в целом, не обязательно на Java. Кроме того, a wait()не является соединением, это касается блокировки объекта, который вызывающий поток пытается получить - он ждет, пока блокировка не будет освобождена другими и не будет получена потоком. Соответственно изменил свой ответ.
einpoklum 06

10

Во-первых, собственно описание

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

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

joinостановит текущий поток до тех пор, пока вызываемый поток не завершит join()выполнение.

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

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


+1. Также обратите внимание, что после вызова yield () все еще нет гарантии, что тот же поток не будет снова выбран для выполнения, учитывая пул потоков с равным приоритетом.
Эндрю Филден

Однако SwitchToThread()call лучше, чем Sleep (0), и это должно быть ошибкой в ​​Java :)
Петър Петров 08

4

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

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

TL; DR;

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

Если не учитывать поддержку HotSpot у зондов USDT (информация о системной трассировке описана в гайде по dtrace ) и свойство 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 в дополнительной проверке существования метода Win32 API SwitchToThread(). Тот же код присутствует для 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 ).


2

Каковы на самом деле основные применения yield ()?

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

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

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

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

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


Просто хотел подтвердить, верно ли это утверждение Without a yield(), once the thread gets control it will execute the 'Inside run' loop in one go? Просьба уточнить.
Абдулла Хан

0

Thread.yield()заставляет поток перейти из состояния «Выполнение» в состояние «Выполнение». Примечание. Это не приводит к переходу потока в состояние «Ожидание».


@PJMeisch, Нет RUNNINGсостояния для java.lang.Threadинстансов. Но это не препятствует естественному «запущенному» состоянию для собственного потока, для которого Threadэкземпляр является прокси.
Соломон Слоу,

-1

Thread.yield ()

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

присоединиться()

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


-4

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

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


Спасибо за ваш ответ. Однако он просто повторяет то, что уже подробно описано в других ответах. Я предлагаю вознаграждение за правильные варианты использования, которые yieldследует использовать.
Петр Пудлак 02
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.