原文: https://howtodoinjava.com/java/multi-threading/difference-between-yield-and-join-in-threads-in-java/

多线程从很长时间以来一直是面试官中非常受欢迎的话题。 尽管我个人觉得很少有人真正有机会在复杂的多线程应用程序上工作(在过去的 7 年中只有一次机会),但仍然可以帮助您方便地掌握这些概念来仅仅增强您的信心。 之前,我讨论过一个类似的问题,关于wait()sleep()方法之间的区别,这一次,我正在讨论join()yield()方法之间的区别 。 坦白地说,我在实践中并未同时使用这两种方法,因此,如果您在任何时候都感到不满意,请提出意见。

Java 线程调度的一些背景知识

需要 Java 虚拟机在其各个线程之间实现可抢占,基于优先级的调度器。 这意味着为 Java 程序中的每个线程分配了一定的优先级,该优先级在明确定义的范围内。 开发人员可以更改此优先级。 Java 虚拟机永远不会更改线程的优先级,即使该线程已经运行了一段时间。

优先级值很重要,因为 Java 虚拟机和底层操作系统之间的约定是操作系统通常必须选择以最高优先级运行 Java 线程。 这就是说 Java 实现基于优先级的调度器时的意思。 此调度器以抢先方式实现,这意味着,当有一个优先级较低的线程正在运行时,当出现优先级较高的线程时,该线程中断(会抢占)。 但是,与操作系统的契约不是绝对的,这意味着操作系统有时可以选择运行优先级较低的线程。(我讨厌有关多线程的问题。)

还要注意, java 并没有要求对其线程进行时间分段,但大多数操作系统都这样做。 这里的术语经常会有一些混乱:抢占经常与时间片混淆。 实际上,抢占仅意味着运行优先级更高的线程而不是优先级较低的线程,但是当线程具有相同优先级时,它们不会相互抢占。 它们通常受时间限制,但这不是 Java 的要求。

了解线程优先级

了解线程优先级是学习多线程的下一个重要步骤,尤其是 yield()的工作方式

  1. 请记住,未指定优先级时,所有线程均具有正常优先级。
  2. 优先级可以在 1 到 10 之间指定。10 是最高优先级,1 是最低优先级,5 是正常优先级。
  3. 请记住,优先级最高的线程将在执行时被赋予优先级。 但是不能保证它一开始就处于运行状态。
  4. 与正在等待机会的池中的线程相比,当前正在执行的线程始终始终具有更高的优先级。
  5. 线程调度器决定应该执行哪个线程。
  6. t.setPriority()可用于设置线程的优先级。
  7. 请记住,应该在调用线程启动方法之前设置优先级。
  8. 您可以使用常量MIN_PRIORITYMAX_PRIORITYNORM_PRIORITY设置优先级。

现在,当我们对线程调度和线程优先级有了一些基本了解时,让我们进入主题。

yield()方法

从理论上讲,yield()是指放弃,屈服,投降。 一个让步线程告诉虚拟机,它愿意让其他线程在其位置进行调度。 这表明它并没有做太重要的事情。 请注意,虽然只是提示,但不能保证完全有效。

yield()Thread.java中定义如下。

  1. /**
  2. * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore
  3. * this hint. Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
  4. * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
  5. */
  6. public static native void yield();

让我们从上面的定义中列出要点:

  • 产量也是静态方法,也是本机方法。
  • Yield告诉当前正在执行的线程给线程池中具有相同优先级的线程一个机会。
  • 无法保证Yield将使当前正在执行的线程立即变为可运行状态。
  • 它只能使线程从运行状态变为可运行状态,而不能处于等待或阻塞状态。

yield()方法示例用法

在下面的示例程序中,我没有特定的原因创建了两个名为生产者和消费者的线程。 生产者设置为最小优先级,而消费者设置为最大优先级。 我将在有/无注释行Thread.yield()的情况下运行以下代码。 如果没有yield(),尽管输出有时会更改,但是通常首先打印所有使用者行,然后才打印所有生产者行。

使用yield()方法,两者都一次打印一行,并且几乎总是将机会传递给另一线程。

  1. package test.core.threads;
  2. public class YieldExample
  3. {
  4. public static void main(String[] args)
  5. {
  6. Thread producer = new Producer();
  7. Thread consumer = new Consumer();
  8. producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
  9. consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority
  10. producer.start();
  11. consumer.start();
  12. }
  13. }
  14. class Producer extends Thread
  15. {
  16. public void run()
  17. {
  18. for (int i = 0; i < 5; i++)
  19. {
  20. System.out.println("I am Producer : Produced Item " + i);
  21. Thread.yield();
  22. }
  23. }
  24. }
  25. class Consumer extends Thread
  26. {
  27. public void run()
  28. {
  29. for (int i = 0; i < 5; i++)
  30. {
  31. System.out.println("I am Consumer : Consumed Item " + i);
  32. Thread.yield();
  33. }
  34. }
  35. }

上面程序的输出,“没有”yield()方法

  1. I am Consumer : Consumed Item 0
  2. I am Consumer : Consumed Item 1
  3. I am Consumer : Consumed Item 2
  4. I am Consumer : Consumed Item 3
  5. I am Consumer : Consumed Item 4
  6. I am Producer : Produced Item 0
  7. I am Producer : Produced Item 1
  8. I am Producer : Produced Item 2
  9. I am Producer : Produced Item 3
  10. I am Producer : Produced Item 4

添加了yield()后上述程序方法的输出

  1. I am Producer : Produced Item 0
  2. I am Consumer : Consumed Item 0
  3. I am Producer : Produced Item 1
  4. I am Consumer : Consumed Item 1
  5. I am Producer : Produced Item 2
  6. I am Consumer : Consumed Item 2
  7. I am Producer : Produced Item 3
  8. I am Consumer : Consumed Item 3
  9. I am Producer : Produced Item 4
  10. I am Consumer : Consumed Item 4

join()方法

可以将Thread实例的join()方法用于将一个线程的执行开始“连接”到另一个线程的执行的结束,这样一个线程只有在另一个线程结束后才能开始运行。 如果在Thread实例上调用join(),则当前正在运行的线程将阻塞,直到Thread实例完成执行为止。

  1. //Waits for this thread to die.
  2. public final void join() throws InterruptedException

join()中设置超时,将使特定超时后的join()效果无效。当达到超时时,主线程和taskThread都是执行任务的同等可能性。 但是,与睡眠一样,join的运行时间也取决于操作系统,因此,您不应假定join会完全按照您指定的时间等待。

像睡眠一样,join通过退出InterruptedException来响应中断。

join()方法示例用法

  1. package test.core.threads;
  2. public class JoinExample
  3. {
  4. public static void main(String[] args) throws InterruptedException
  5. {
  6. Thread t = new Thread(new Runnable()
  7. {
  8. public void run()
  9. {
  10. System.out.println("First task started");
  11. System.out.println("Sleeping for 2 seconds");
  12. try
  13. {
  14. Thread.sleep(2000);
  15. } catch (InterruptedException e)
  16. {
  17. e.printStackTrace();
  18. }
  19. System.out.println("First task completed");
  20. }
  21. });
  22. Thread t1 = new Thread(new Runnable()
  23. {
  24. public void run()
  25. {
  26. System.out.println("Second task completed");
  27. }
  28. });
  29. t.start(); // Line 15
  30. t.join(); // Line 16
  31. t1.start();
  32. }
  33. }
  34. Output:
  35. First task started
  36. Sleeping for 2 seconds
  37. First task completed
  38. Second task completed

仅此一个很小但很重要的概念。 在评论部分让我知道您的想法。

祝您学习愉快!