在篮球比赛中,我们经常会看到两队选手互相抢篮球,当某个选手抢到篮球后就可以拍一会,之后他会把篮球让出来,其他选手重新开始抢篮球,这个过程就相当于 Java 程序中的线程让步。所谓的线程让步是指正在执行的线程,在某些情况下将 CPU 资源让给其他线程执行。
线程让步可以通过 yield()方法来实现,该方法和 sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于 yield()方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用 yield()方法之后,只有与当前线程优先级相同或者更高的线程才能得到执行的机会。
接下来通过一个案例来演示一下 yield()方法的使用,如下所示。
public class example08 {
public static void main(String[] args) {
//创建两个线程
Thread t1 = new YieldThread("线程A");
Thread t2 = new YieldThread("线程B");
//开启两个线程
t1.start();
t2.start();
}
}
//定义 YieldThread 类继承 Thread 类
class YieldThread extends Thread{
//定义一个有参的构造方法
public YieldThread(String name) {
super(name); //调用父类的构造方法
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
if (i == 5) {
System.out.print("线程让步:");
Thread.yield(); //线程运行到此,作出让步
}
}
}
}
运行结果如下所示。书上的解释出现了错误,后翻阅资料得知:(2020/7/26 update 书上的解释没有错误,是我当时理解错了)
~~
上述代码块中创建了两个线程 t1 和 t2,它们的优先级相同。两个线程在循环变量 i 等于 5 时,都会调用 Thread 的yield()方法,使当前的线程暂停,这时另一个线程就会获得执行。从运行结果可以看出,当线程 A 输出 5 后,会做出让步,线程 A 继续执行。同样,线程 B 输出 5 后,也会做出让步,线程 A 继续执行。
yield()方法只是使当前线程从执行状态(运行状态)回到可执行状态(就绪状态),执行完 yield()的线程有可能在进入到可执行状态又立刻被执行。另外 yeild()方法只能使同优先级或者更高优先级的线程得到执行机会(这是个错误的结论,故打上删除线)
对于线程的运行,它有一个时间片的概念,线程优先级低的在拿到时间片时也是可以执行的,只不过优先级高的线程会尽可能早的执行罢了,不是说优先级高的一定会先执行。
抢占式调度模式时,高级别的线程优先执行,如果低级别的线程正在运行,有高级别线程可运行状态,则会执行完低级别线程,再去执行高级别线程。如果低级别线程处于等待、睡眠、阻塞状态,可以调用yield()函数让当前运行线程回到可运行状态,以允许具有相同优先级或者高级别的其他线程获得运行机会。
因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证 yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
所以,对于 yield()方法,可以理解为当前线程是让出了执行权,但是还是要看时间片分到了谁。就算当前线程现在让出了执行权,可能马上它又得到执行权接着执行。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但可能没有效果。
举个例子:
一帮朋友在排队上公交车,轮到 Yield 的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。
然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是 Yield 先上车了。
虽然线程是有优先级的,但是优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。
所谓的优先级执行,是在大量执行次数中才能体现出来的。