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

由于过于笼统,这个问题应该结束。
2014年

yield()不可以。有和没有时,它不会返回相同的结果。当我的i大于5时,可以看到yield()method 的效果。
lakshman 2014年

Answers:


97

资料来源:http : //www.javamex.com/tutorials/threads/yield.shtml

视窗

在Hotspot实现中,Thread.yield()Java 5和Java 6之间的工作方式发生了变化。

在Java 5中,Thread.yield()调用Windows API调用Sleep(0)。这具有清除当前线程的数量并将放在优先级级别的队列末端的特殊效果。换句话说,具有相同优先级的所有可运行线程(以及具有更高优先级的那些线程)将有机会在下一个给定的CPU时间之前让产生的线程运行。当最终对其进行重新计划时,它将返回完整的完整量子,但不会从屈服之时“继承”任何剩余的量子。此行为与非零睡眠略有不同,在非零睡眠中,睡眠线程通常会丢失1个量子值(实际上是10或15ms滴答的1/3)。

在Java 6中,此行为已更改。现在,热点VM Thread.yield()使用Windows SwitchToThread()API调用实现。该调用使当前线程放弃其当前时间片,而不是其整个时间片。这意味着,可以根据其他线程的优先级,在一个中断周期后将回退线程调度回去。(有关时间片的更多信息,请参见线程调度部分。)

的Linux

在Linux下,热点仅调用sched_yield()。此调用的结果略有不同,并且可能比Windows下更为严重:

  • 直到所有其他线程都具有一个 CPU片时,产生的线程才会获得另一个CPU ;
  • (至少在内核2.6.8及更高版本中),调度程序在其最近的CPU分配上的试探法隐含地考虑了线程已产生的事实,因此,隐式地,在执行以下操作时,可以给已产生的线程更多的CPU资源:未来。

(有关优先级和调度算法的更多详细信息,请参见线程调度部分。)

什么时候使用yield()

几乎不会说。它的行为不是标准定义的,通常有更好的方法可以执行您可能要通过yield()执行的任务:

  • 如果您尝试使用一部分CPU,则可以通过控制线程在最后一个处理块中使用了多少CPU,然后休眠一段时间来进行补偿,以更可控的方式进行操作:请参见的睡眠()方法;
  • 如果您正在等待进程或资源完成或变得可用,则有更有效的方法来完成此操作,例如通过使用join()等待另一个线程完成,使用等待/通知机制允许一个线程通知另一个任务已完成,或者最好使用Java 5并发结构之一(例如信号量阻塞队列)来通知任务。

18
“剩余量子”,“整个量子”-有人忘记了“量子”一词的含义
kbolino

@kbolino Quantum是新原子。
Evgeni Sergeev

2
@kbolino-... 拉丁语:“尽可能多”,“多少”。我看不出这与上面的用法有何矛盾。这个词仅表示已描述数量的东西,因此对我来说,将其分为使用过的部分和剩余部分似乎是完全合理的。
Periata Breatta '16

@PeriataBreatta我想,如果您熟悉物理学以外的单词,这会更有意义。物理定义是我所知道的唯一一个。
kbolino '16

我对这个问题悬赏,以使该答案的更新为7、8、9。使用有关7,8和8的当前信息对其进行编辑,您将获得赏金。

40

我看到悬赏金重新激活了这个问题,现在问它的实际用途yield是什么。我将以我的经验为例。

众所周知,yield强制调用线程放弃正在运行的处理器,以便可以安排另一个线程运行。当当前线程暂时完成其工作但想要快速返回队列的前面并检查某些条件是否已更改时,此功能很有用。这与条件变量有何不同?yield使线程可以更快地返回到运行状态。在等待条件变量时,线程被挂起,并且需要等待其他线程发出信号以指示它应该继续。yield基本上说“允许运行一个不同的线程,但是请允许我很快恢复工作,因为我希望状态会非常快地发生变化”。这暗示着忙碌的纺纱,在这种情况下,条件可能会迅速改变,但是挂起线程会导致性能下降。

但是有足够的胡言乱语,这是一个具体的例子:波前平行模式。此问题的一个基本实例是在填充有0和1的二维数组中计算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是相当精细的优化。在错误的地方使用它,例如在很少变化的条件下等待,将导致CPU的过度使用。

抱歉,很长一段话,希望我能说清楚。


1
IIUC是您在文档中介绍的内容,其想法是,在这种情况下,忙等待更为有效,yield即在不满足条件的情况下进行调用以使其他线程有机会继续进行计算,而不是使用更高的级别同步原语,对不对?
PetrPudlák2014年

3
@PetrPudlák:是的。我将其与使用线程信号进行了基准测试,在这种情况下,性能差异很大。由于条件可以很快变为真(这是关键问题),因此条件变量太慢,因为操作系统将线程置于线程保持状态,而不是使用来在很短的时间内放弃CPU yield
2014年

@Tudor很棒的解释!
开发人员MariusŽilėnas16年

1
“注意一下sleep(0)的用法,它的使用率或多或少等于C的产量。” ..好吧,如果您想在Java中使用sleep(0),为什么不使用它呢?Thread.sleep()已经存在。我不确定这个答案是否提供了为什么人们会使用Thread.yield()而不是Thread.sleep(0)的理由;还有一个现有的线程解释了为什么它们不同。
eis

@eis:Thread.sleep(0)与Thread.yield()超出了此答案的范围。我只是为那些正在寻找与C语言相当相似的人提到Thread.sleep(0)。问题是关于Thread.yield()的用法。
Tudor

12

关于之间的差异yield()interrupt()以及join()-在一般情况下,不只是在Java中:

  1. 屈服:从字面上看,“屈服”意味着放手,放弃,投降。产生性线程告诉操作系统(或虚拟机,或不包括什么),它愿意让其他线程代替它进行调度。这表明它并没有做太重要的事情。不过,这只是一个提示,并不保证会产生任何效果。
  2. 加盟:当多个线程对某些手柄,或令牌,或实体“加入”,所有的人等待,直到所有其他相关的线程都执行完毕(完全或高达自己对应的连接)。这意味着一堆线程都已完成其任务。然后,可以安排这些线程中的每个线程继续进行其他工作,并能够假设所有这些任务确实完成了。(不要与SQL Join混淆!)
  3. 中断:由一个线程用来“戳”正在休眠,等待或加入的另一个线程-以便可以安排它再次继续运行,也许表明它已被中断。(不要与硬件中断混淆!)

对于Java,请参阅

  1. 加盟:

    如何使用Thread.join?(在StackOverflow上)

    什么时候加入线程?

  2. 屈服:

  3. 中断:

    Thread.interrupt()邪恶吗?(在StackOverflow上)


加入句柄或令牌是什么意思?wait()和notify()方法位于Object上,从而允许用户等待任何任意Object。但是join()似乎不太抽象,需要在继续执行之前在要完成的特定线程上调用...是吗?
spaaarky21 2014年

@ spaaarky21:我的意思是一般而言,不一定是Java。同样,a wait()不是联接,它与调用线程试图获取的对象上的锁有关-等待直到其他人释放该锁并被该线程获取。相应地调整了我的答案。
einpoklum 2014年

10

首先,实际描述是

使当前正在执行的线程对象暂时暂停并允许其他线程执行。

现在,您的主线程很有可能会在执行run新线程的方法之前执行该循环五次,因此所有对它的调用yield将仅在执行主线程中的循环之后发生。

join将停止当前线程,直到被调用的线程join()执行完毕。

interrupt将中断正在调用的线程,从而导致InterruptedException

yield 允许上下文切换到其他线程,因此该线程将不会消耗该进程的整个CPU使用率。


+1。还要注意,在调用yield()之后,给定优先级相同的线程池,仍然无法保证不会再次选择同一线程来执行。
安德鲁·菲尔登

然而, SwitchToThread()电话是不是睡眠(0)好,这应该是在Java中:)错误
ПетърПетров

4

当前答案已过期,鉴于最近的更改,需要修订。

从6到9,Java版本之间没有实际差异Thread.yield()

TL; DR;

基于OpenJDK源代码(http://hg.openjdk.java.net/)的结论。

如果不考虑对USDT探针的HotSpot支持(dtrace指南中描述了系统跟踪信息)和JVM属性,ConvertYieldToSleep则的源代码yield()几乎相同。请参阅下面的说明。

Java 9

Thread.yield()调用特定 于OS的方法os::naked_yield()
在Linux上:

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

在Windows上:

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

Java 8和更早版本:

Thread.yield()调用特定 于OS的方法os::yield()
在Linux上:

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

在Windows上:

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

如您所见,Thread.yeald()Linux在所有Java版本上都是相同的。
让我们来看一下os::NakedYield()JDK 8中的Windows :

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中存在相同的代码。JDK7中的
源代码os::NakedYield()略有不同,但是具有相同的行为:

    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向CPU建议您可以停止当前线程并以更高的优先级开始执行线程。换句话说,为当前线程分配一个低优先级值,以便为更多关键线程留出空间。

我相信下面的代码在使用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方法不会使线程进入Wait或Blocked状态。它只能使线程从运行状态变为可运行状态。

加入()

当线程实例调用join时,该线程将告诉当前正在执行的线程等待,直到Joining线程完成。在需要在当前任务完成之前完成任务的情况下使用联接。


-4

yield()主要用于搁置多线程应用程序。

所有这些方法的不同之处在于yield()在执行另一个线程时将线程置于保留状态,并在该线程完成后返回,join()将使线程的开始处一直执行直到结束,而另一个线程在该线程执行后运行结束后,interrupt()将在一段时间内停止执行线程。


谢谢您的回答。但是,它只是重复了其他答案已经详细描述的内容。我提供赏金用于适当的用例yield
PetrPudlák14年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.