1.1.3 Java线程使用到了哪些调度策略?

Java线程调度主要依赖于底层操作系统的线程调度机制和JVM的实现。因此,具体的线程调度策略可能会根据操作系统和JVM的不同而有所差异。常见的线程调度策略包括“时间片轮转调度”“优先级调度”“抢占式调度”。

(1)时间片轮转调度。

在时间片轮转调度策略中,每个线程被分配一个固定长度的时间段,这个时间段称为“时间片”。所有可运行的线程轮流使用CPU(Central Processing Unit,中央处理器)资源,每个线程在其分配的时间片内运行。如果线程在其时间片用完之前完成了任务,它将释放CPU;如果线程的时间片用完了,该线程会被暂停,操作系统会将CPU分配给下一个线程。时间片转轮调度尝试给每个线程分配公平的CPU时间。

(2)优先级调度。

在优先级调度策略中,每个线程都有一个优先级。当多个线程可运行时,具有最高优先级的线程将首先获得CPU。Java提供了1~10这10个不同的优先级,通过Thread类的setPriority(int)方法设置线程的优先级。然而,优先级的实际效果高度依赖操作系统的调度策略,某些操作系统可能会忽略这些优先级或只是粗略地实现。

(3)抢占式调度。

在实践中,大多数现代操作系统使用的是一种叫作“抢占式多任务处理”的调度算法,它结合了时间片轮转和优先级两种方式。操作系统会根据线程的优先级来分配CPU资源,但同时也会在必要时通过时间片轮转来确保资源的公平分配。

在日常开发中,我们可以使用一些线程控制方法,比如yield()和sleep()等。这些方法调用并不是直接绑定到特定的线程调度策略(如时间片轮转调度或优先级调度),它们与线程调度策略的关系更多地取决于底层操作系统如何实现线程调度,以及JVM如何在该操作系统上工作。下面我们对yield()和sleep()两个方法进行详细解释。

(1)yield()方法。

yield()方法是一种提示性的方法,它提示调度器当前线程愿意让出其当前的CPU使用权。但是,它只给出一个提示,而调度器可能会忽略这个提示。如果调度器接受这个提示,那么当前线程会从运行状态转移到就绪状态,从而允许具有相同优先级的其他线程获得执行机会。不过,调度器可能会立即重新调度这个刚刚让出CPU的线程。yield()方法的行为在很大程度上依赖于具体的操作系统和JVM实现。

(2)sleep()方法。

sleep()方法使当前线程暂停执行指定的时间(以毫秒为单位),使线程进入超时等待状态,但该线程不会释放任何锁资源。调用sleep()意味着线程至少需要等待指定的时间后才能再次进入可运行状态。一旦指定的时间过去,线程就会进入可运行状态,等待调度器的调度。sleep()的使用不依赖于线程调度策略,但是线程从超时等待状态“醒来”并变为可运行状态后,在何时开始运行将取决于操作系统的线程调度策略。

虽然Java允许开发者设置线程的优先级,但这些优先级的实际效果和表现依赖于JVM和操作系统的具体实现。建议开发者不要仅依赖于线程优先级来实现关键的功能逻辑,因为代码在不同的平台上可能会有不同的行为表现。可以使用同步控制、锁、并发容器、并发集合等技术,提供更具确定性的方式来编写并发程序,从而降低代码在不同平台上行为不一致的风险。

总的来说,线程控制方法的作用与操作系统的线程调度策略有关,但它们本身并不指定使用哪种线程调度策略。它们的行为将受到当前操作系统的线程调度算法和JVM实现的影响。由于JVM也运行在宿主操作系统之上,因此它也依赖于操作系统的线程调度策略。