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


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

9 ответов:

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

Windows

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

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

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

Linux

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

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

(см. раздел резьба планирование для получения более подробной информации о приоритетах и алгоритмы планирования.)

при использовании yield()?

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

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

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

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

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

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

здесь у нас есть два острова 1s: сверху слева и снизу справа.

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

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.

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

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

извините за длинный лепет, надеюсь, я сделал сам четкий.

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

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

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

  1. вступление:

    как использовать поток.присоединиться? (здесь на StackOverflow)

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

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

  3. прерывания:

    Резьба.прервать() зло? (здесь на StackOverflow)

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

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

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

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

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

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

Каковы, по сути, основные виды использования yield()?

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

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

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

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

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

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

нет практические разница 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();
}

On Windows:

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

Как видите, Thread.yeald() на Linux идентичен для всех версий Java.
Давайте посмотрим окна 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).

Thread.yield() заставляет поток переходить из состояния" работает "в состояние" Runnable". Примечание: это не заставляет поток перейти в состояние "ожидания".

нить.выход ()

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

join ()

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

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

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