Java并发性– yield()和join()之间的区别
Java 并发性:yield() 和 join() 之间的区别
多线程长期以来一直是读者非常关注的话题。虽然个人经验表明,真正从事复杂多线程应用程序开发的机会并不多(在过去 7 年中我仅遇到过一次),但熟悉这些概念仍能增强您处理并发问题的信心。此前,我曾讨论过 wait() 和 sleep() 方法之间的差异,本文将重点探讨 join() 和 yield() 方法之间的区别。坦白地说,我在实际生产环境中同时使用这两种方法的场景较少,如果您有任何补充或不同见解,欢迎在评论区提出。
Java 线程调度背景
Java 虚拟机(JVM)需要在其各个线程之间实施基于优先级的抢占式调度程序(Priority-based Preemptive Scheduler)。这意味着 Java 程序中的每个线程都被分配了一个优先级,该优先级在明确定义的范围内,且开发人员可以更改此优先级。JVM 本身不会更改线程的优先级,即使该线程已经运行了一段时间。
优先级值至关重要,因为 JVM 与底层操作系统之间的约定是:操作系统通常必须选择以最高优先级运行 Java 线程。这就是 Java 实现基于优先级调度程序的含义。此调度程序以抢占式(Preemptive)方式实现,意味着当出现更高优先级的线程时,它会中断(抢占)当时运行的更低优先级的线程。
然而,与操作系统的契约并非绝对的。这意味着操作系统有时可能选择运行优先级较低的线程。
注意:关于多线程的一个常见困惑是“没有保证”。确实,线程调度的具体行为高度依赖于底层操作系统和 JVM 实现。
此外,Java 并不强制要求对其线程进行时间分片(Time-slicing),但大多数操作系统都会这样做。这里术语经常混淆:抢占经常与时间片混淆。实际上,抢占仅意味着优先级较高的线程优先于优先级较低的线程运行;当线程具有相同优先级时,它们不会相互抢占。它们通常受时间片限制,但这并非 Java 语言的强制要求。
理解线程优先级
了解线程优先级是学习多线程的下一个重要步骤,尤其是理解 yield() 的工作方式。以下是关于线程优先级的关键点:
- 未指定优先级时,所有线程均具有正常优先级(Normal Priority)。
- 优先级可以在 1 到 10 之间指定。10 是最高优先级,1 是最低优先级,5 是正常优先级。
- 优先级最高的线程在执行时会被优先调度,但不能保证它一开始就处于运行状态。
- 与正在等待机会的池中的线程相比,当前正在执行的线程始终具有更高的优先级。
- 线程调度程序决定应该执行哪个线程。
- 可使用
t.setPriority()设置线程的优先级。 - 优先级应该在线程启动(
start())之前设置。 - 可以使用常量
MIN_PRIORITY、MAX_PRIORITY和NORM_PRIORITY设置优先级。
在对线程调度和线程优先级有了基本了解后,让我们进入主题。
yield() 方法
从理论上讲,“屈服”(Yield)意味着放手、放弃或投降。yield() 方法告诉虚拟机,当前线程愿意让出处理器使用权,以便其他线程可以被调度。这表明该线程当前并没有做太重要的事情。
注意:这只是一个提示(Hint),并不保证完全有效。
yield() 在 Thread.java 中定义如下:
/**
* 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 this hint.
* Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilize a CPU.
* Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
*/
public static native void yield();从上述定义中,我们可以列出以下要点:
yield()是静态方法,也是本地方法(native)。yield()告诉当前正在执行的线程,给线程池中具有相同优先级的线程一个机会。- 无法保证
yield()将使当前正在执行的线程立即变为可运行状态。 - 它只能使线程从运行状态(Running)变为可运行状态(Runnable),而不能处于等待(Waiting)或阻塞(Blocked)状态。
yield() 方法示例用法
在下面的示例程序中,我创建了两个线程,分别称为生产者(Producer)和消费者(Consumer)。生产者设置为最小优先级,而消费者设置为最大优先级。我将分别在有无 Thread.yield() 调用的情况下运行以下代码。
- 没有
yield()时:尽管输出有时会更改,但通常首先打印所有消费者行,然后才打印所有生产者行(因为消费者优先级高)。 - 使用
yield()方法:两者交替打印,几乎总是将机会传递给另一线程。
package com.daicy.concurrency.thread;
/**
* @author: create by daichangya
* @version: v1.0
* @description: com.daicy.concurrency.thread
* @date: 20-4-17
*/
public class YieldTest {
public static void main(String[] args) {
Thread producer = new Producer();
Thread consumer = new Consumer();
producer.setPriority(Thread.MIN_PRIORITY); // 最小优先级
consumer.setPriority(Thread.MAX_PRIORITY); // 最大优先级
producer.start();
consumer.start();
}
}
class Producer extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("I am Producer : Produced Item " + i);
Thread.yield();
}
}
}
class Consumer extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("I am Consumer : Consumed Item " + i);
Thread.yield();
}
}
}不使用 yield() 方法的输出示例:
I am Producer : Produced Item 0
I am Producer : Produced Item 1
I am Producer : Produced Item 2
I am Producer : Produced Item 3
I am Producer : Produced Item 4
I am Consumer : Consumed Item 0
I am Consumer : Consumed Item 1
I am Consumer : Consumed Item 2
I am Consumer : Consumed Item 3
I am Consumer : Consumed Item 4使用 yield() 方法的输出示例:
I am Producer : Produced Item 0
I am Consumer : Consumed Item 0
I am Producer : Produced Item 1
I am Consumer : Consumed Item 1
I am Producer : Produced Item 2
I am Consumer : Consumed Item 2
I am Producer : Produced Item 3
I am Consumer : Consumed Item 3
I am Producer : Produced Item 4
I am Consumer : Consumed Item 4join() 方法
线程实例的 join() 方法可用于将一个线程的执行“联接”到另一个线程的结束。这意味着调用 join() 的线程将等待被调用线程完成执行后才继续运行。
如果在 Thread 实例上调用 join(),则当前正在运行的线程将阻塞,直到该 Thread 实例完成执行。
// 等待此线程死亡
public final void join() throws InterruptedException在 join() 中设置超时参数,将使特定超时时间后 join() 的等待效果失效。当达到超时时,主线程和被 join 的线程执行的可能性同等。但是,与 sleep() 一样,join() 的运行时间也取决于操作系统,因此您不应假定 join() 会完全按照您指定的时间等待。
像 sleep() 一样,join() 通过抛出 InterruptedException 来响应中断。
join() 方法示例用法
package com.daicy.concurrency.thread;
/**
* @author: create by daichangya
* @version: v1.0
* @description: com.daicy.concurrency.thread
* @date: 20-4-17
*/
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("First task started");
System.out.println("Sleeping for 2 seconds");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("First task completed");
}
});
Thread t1 = new Thread(new Runnable() {
public void run() {
System.out.println("Second task completed");
}
});
t.start();
t.join(); // 主线程等待 t 执行完毕
t1.start();
}
}输出:
First task started
Sleeping for 2 seconds
First task completed
Second task completed仅此一个很小但很重要的概念。欢迎在评论部分分享您的想法。
学习愉快!
说明:本文基于 Java 基础线程 API 编写。在现代 Java 并发编程中,建议优先使用 java.util.concurrent 包下的高级工具类(如 ExecutorService、CountDownLatch 等)来管理线程生命周期和同步,而非直接操作底层 Thread 类。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/java-bing-fa-xing--yield-he-join-zhi-jian-de-qu-bie.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。