[翻译]Java的线程优先级介绍

2018-01-28  本文已影响64人  徐士林

原文,侵删
本文讨论Java虚拟机(JVM)如何将Java执行线程的优先级映射道Solaris系统的本地线程优先级。文章涵盖了Solaris线程和JVM之前和现在对此的实现机制。

背景信息:Java线程

JVM为线程定义了10个逻辑性优先级范围,包括

  • java.lang.Thread.MIN_PRIORITY = 1
  • java.lang.Thread.MORM_PRIORITY = 5
  • java.lang.Thread.MAX_PRIORITY = 10

Java线程[1-10]的优先级范围可以通过Thread.setPriority(int)方法进行设置。Java线程的默认优先级是NORM_PRIORITY。 A JVM is free to implement priorities in any way it chooses, including ignoring the value.

JVM目前将每个Java线程与本地的线程一对一关联。本地线程和Java线程的关系是稳定的并且在Java线程的生命周期中保持不变。

背景信息:Solaris

Libthread 变体:T1 and T2

在Solaris 9之前,默认的libthread是所谓的T1 libthread。T1提供了一个M:N的线程模型,期中M个本地线程被复用在N个内核线程上(LWPs)。本地线程和LWPs的关系是动态的,即使正在运行的本地线程,也可能在其未感知的状态下切换到另一个内核线程中运行。Solaris提供了 priocntl()这样一个系统调用可以更改LWP的优先级,但是因为本地线程和LWP的关系是不牢靠的,所以没有可靠的办法去更改本地线程的优先级(JVM可以更改LWP的优先级,但是Java线程可以在JVM不知情的情况下切换到另一个LWP上执行)

T2是Solaris 9和更高版本中的默认libthread,它实现了一个更简单,更健壮的1:1线程模型。每一个本地线程都和LWP一对一绑定,并且该关系在本地线程的生命周期中是稳定的。

Both T1 and T2 expose a thr_setprio() API that applications use to set a thread's process-local priority.给thr_setprio()方法赋的值是进程本地属性,对内核的调度是不可见的。thr_setprio()设置的优先级仅控制线程在用户级别的process-local睡眠队列(如线程争用进程中互斥性质资源是进入的睡眠队列)中的位置和顺序。在HotSpot中的互斥体通常是不会有竞争的,条件变量一般只有0个或1个线程。所以,thr_setprio()控制的线程位置对大多数Java线程几乎没有影响。thr_setprio()函数支持从0到127(含)的优先级值,其中127代表最高优先级。

T1也使用线程优先级来实现基本的用户模式抢占。T1 maintains an invariant that the thr_setprio()-priority of any thread on the local ready queue must be less than or equal to the priority of any unbound thread that's currently running and associated with an LWP. If the invariant is threatened, T1 preempts the lowest priority executing thread to "steal" its LWP in order to reestablish the invariant.

在这些情况下可以发生抢占:

  • 正在运行的线程降低自己的优先级
  • 正在运行的线程增加就绪线程的优先级
  • 当一个比任何正在运行线程优先级都高的线程转换成ready状态时可能发生抢占。例如,Java线程退出监视器或调用notify时,JVM正在使用用户级别的Solaris同步原语。

本地线程优先级

在Solaris中,LWPs的线程优先级影响到该线程相比于其他线程获得的CPU时钟周期数。Solaris调度程序使用优先级(以及其他因素)来确定一个线程是否应该抢占另一个线程,线程运行的频率以及线程运行的时间。LWP的优先级由priocntl()系统调用指定。

总结

回顾一下,我们用Thread.setPriority方法色设置Java线程的优先级。Java线程在本地线程上运行。thr_setprio()方法可以更改本地线程的优先级。本地方法运行在LWPs上。priocntl()系统调用可以更改LWPs的优先级

之前版本中的线程优先级实现

1.4.2之前

在1.4.2之前,当Java线程被创建或者调用Thread.setPriority方法时,HotSpot会调用thr_setprio()方法把Java线程的优先级映射到本地优先级上。调用thr_setprio()方法在Java的线程行为上几乎没有影响。JVM不会调用priocntl()方法更改底层的LWP。这是一个有意识的设计,因为在1.4.2之前,Solaris上唯一可用的libthread只有T1 libthread。

[注意] JVM可以通过在创建线程时指定THR_BOUND来强制将Java线程以1:1绑定道T1下的LWP。THR_BOUND is not sufficient, however, as threads that attach to the JVM might not be THR_BOUND and the primordial thread is not THR_BOUND. 鉴于Solaris系统线程在创建后无法强制绑定,HotSpot实现者认为当Java线程调用setPriority()时,不要改变LWP的优先级

1.4.2

在1.4.2中,HotSpot在启动是可以选择有T1和T2可供选择。如果JVM选择在T1下启动,则线程优先级和早期版本完全相同。

在T2模式下,Thread.setPriority()调用会转化成对thr_setprio()和priocntl()两个方法的调用。thr_setprio()改变进程本地级别的线程优先级(Solaris系统无法感知其优先级),priocntl()方法改变底层LWP调度的优先级。JVM仅对在TS(分时),IA(交互式),RT(实时),调度类中运行的线程调用priocntl()。有关调度类的说明,请参阅Solaris priocntl(2)的手册页。

不幸的是,TS和IA调度类中本地线程的默认优先级是可能的最高优先级。Java线程的默认逻辑优先级是NORM_PRIORITY,这只是Java线程优先级的中间值。当把JVM中的优先级NORM_PRIORITY映射成本地线程和LWP的优先级时,结果会是一个小于本地优先级默认值的值。假设我们有一个2-CPU的系统运行一个JVM程序,并且这个JVM有两条优先级为NORP_PRIORITY的线程在运行。假设线程在IA或者TS的调度类中运行(通常情况也是如此)。当创建Java线程时,JVM调用priocntl()将NORM_PRIORITY映射到TS或IA优先级范围的中间值。此外,假设另一个进程中的2个C语言线程正在与Java线程并发的处于运行或者就绪状态。C线程和Java线程对应的本地线程都是CPU绑定的。本地线程将在TS或IA调度类中以最高优先级运行,并且JVM线程将以中等优先级运行。由于所有四个线程都在竞争CPU周期,因此本地线程将获得相对较多的CPU周期,并且Java线程在某种意义上将处于劣势。只有当Java线程与正常线程竞争并且系统完全饱和时,才会出现此效应。

Conversely, a benefit of using lower relative priorities is that in the TS and IA scheduling classes a thread running at lower priorities receives a longer quantum, assuming that it's not preempted mid-quantum by higher priority threads that become ready。由于抢占的上下文切换速率降低,所以更长的quantum通常对服务器应用中的线程有益。A thread is permitted to run on a CPU for a longer period and the cache-reload transient (the period immediately after a thread is scheduled on to a CPU: when a thread incurs a high cache miss rate as it repopulates the CPU's data caches and displaces the previous thread's data) is amortized over a longer quantum.

随笔

下面几条内容适用于所有版本的JVM

上一篇下一篇

猜你喜欢

热点阅读