从Java到JVM到OS线程的优先级

前言

Java 的线程的调度机制由 JVM 实现,假如有若干条线程,你想让某些线程拥有更长的执行时间,或某些线程分配少点执行时间,这时就涉及“线程优先级”。

优先级别

Java 把线程优先级分成10个级别,线程被创建时如果没有明确声明则使用默认优先级,JVM 将根据每个线程的优先级分配执行时间的概率。有三个常量Thread.MIN_PRIORITYThread.NORM_PRIORITYThread.MAX_PRIORITY分别表示最小优先级值(1)、默认优先级值(5)、最大优先级值(10)。

由于 JVM 的实现以宿主操作系统为基础,所以 Java 优先级值与各种不同操作系统的原生线程优先级必然存在某种映射关系,这样才足以封装所有操作系统的优先级提供统一优先级语义。例如1-10优先级值在 Linux 可能要与-20-19优先级值进行映射,而 Windows 系统则有9个优先级要映射。

优先级高先执行?

我们能否用优先级值的大小来控制线程的执行顺序呢?答案明显是不能的。这是因为影响线程优先级的因素有很多,包括:

  • 不同版本的操作系统和 JVM 都可能行为不相同。
  • 优先级对于不同操作系统调度器的意义可能不相同。
  • 有些操作系统的调度器不支持优先级。
  • 对于操作系统,线程的优先级存在“全局”和“本地”之分,一般不同进程的优先级相互独立。
  • 前面提到过,不同的操作系统优先级定义的值不一样,而 Java 只定义1-10。
  • 操作系统常常会对长时间得不到运行的线程给予增加一定的优先级。
  • 操作系统的线程调度器可能会在线程发生等等时有一定的临时优先级调整策略。

一个例子

下面一个简单例子,两个线程每次运行的结果可能都不相同。

public class ThreadPriorityTest {

	public static void main(String[] args) {
		Thread t = new MyThread();
		t.setPriority(10);
		t.setName("00");
		Thread t2 = new MyThread();
		t2.setPriority(8);
		t2.setName("11");
		t2.start();
		t.start();
	}

	static class MyThread extends Thread {
		public void run() {
			for (int i = 0; i < 5; i++)
				System.out.println(this.getName());
		}
	}
}
复制代码
11
00
00
00
00
00
11
11
11
11
复制代码

setPriority方法

该方法用于设置优先级,逻辑为:

  • 检查是否有权限访问该线程。
  • 检查优先级值的合法性,必须在MIN_PRIORITYMAX_PRIORITY之间。
  • 不能超过线程组的最大优先级值。
  • 调用setPriority0本地方法。
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }
    
private native void setPriority0(int newPriority);
复制代码

本地实现

Java 层声明的本地方法对应实现在 Thread.c 中,setPriority0是一个注册到 JVM 中的方法,它与 JVM 中的JVM_SetThreadPriority函数绑定了,所以实现逻辑在JVM_SetThreadPriority函数里。逻辑为:

  • JVMWrapper("JVM_SetThreadPriority")用于调试。
  • 通过 MutexLocker 获取互斥锁。
  • 转成 JVM 层使用的 oop 对象,它是 JVM 中对 Java 层 Thread 对象的描述。
  • 设置 oop 对象的优先级属性值,这里通过java_lang_Thread::set_priority来设置,即java_thread->int_field_put(_priority_offset, priority),这里是通过计算 oop 对象中 priority 属性存储的偏移地址,然后将值设置到该地址。
  • 通过java_lang_Thread::thread获取 JavaThread 指针,即(JavaThread*)java_thread->address_field(_eetop_offset),其中通过计算 eetop 偏移来获取,eetop 属于 Java 层的 Thread 类中的属性。可以这样做的原因是 JavaThread 对象维护了一个指向 oop 的指针,而 oop 也同样维护了一个指向 JavaThread 对象的指针。
  • 最后调用Thread::set_priority来设置操作系统级别的线程优先级,通过调用os::set_priority来实现。
static JNINativeMethod methods[] = {
    ...
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    ...
};

JVM_ENTRY(void, JVM_SetThreadPriority(JNIEnv* env, jobject jthread, jint prio))
  JVMWrapper("JVM_SetThreadPriority");
  MutexLocker ml(Threads_lock);
  oop java_thread = JNIHandles::resolve_non_null(jthread);
  java_lang_Thread::set_priority(java_thread, (ThreadPriority)prio);
  JavaThread* thr = java_lang_Thread::thread(java_thread);
  if (thr != NULL) {                  
    Thread::set_priority(thr, (ThreadPriority)prio);
  }
JVM_END
复制代码
void Thread::set_priority(Thread* thread, ThreadPriority priority) {
  debug_only(check_for_dangling_thread_pointer(thread);)
  (void)os::set_priority(thread, priority);
}
复制代码

接着看os::set_priority函数的实现:

  • 首先判断优先级值的合法性。
  • 其次是通过java_to_os_priority将 Java 层的优先级映射成操作系统的优先级,各种操作系统不相同,在下面“优先级映射”中细讲。
  • 最后将调用set_native_priority函数设置线程优先级,各种操作系统不相同,在下面“OS线程优先级设置”中细讲。
OSReturn os::set_priority(Thread* thread, ThreadPriority p) {
  if (p >= MinPriority && p <= MaxPriority) {
    int priority = java_to_os_priority[p];
    return set_native_priority(thread, priority);
  } else {
    assert(false, "Should not happen");
    return OS_ERR;
  }
}
复制代码

优先级映射

Java 层的优先级值需要转成操作系统的优先级值,这中间存在一个映射操作,下面看怎么映射?前面说到通过java_to_os_priority转换,它是一个数组,这个数组一共有12个元素。下面看 Linux 和 Windows 操作系统的值:

对于Linux

Java 层的1,10分别对应 Linux 的4和-5,Linux 线程的优先级值范围是 -20到19,其中-20位最高优先级,19位最低优先级,Java 则是使用了-5到4来映射1到10的优先级。

int os::java_to_os_priority[CriticalPriority + 1] = {
  19,              // 0 Entry should never be used
   4,              // 1 MinPriority
   3,              // 2
   2,              // 3
   1,              // 4
   0,              // 5 NormPriority
  -1,              // 6
  -2,              // 7
  -3,              // 8
  -4,              // 9 NearMaxPriority
  -5,              // 10 MaxPriority
  -5               // 11 CriticalPriority
};
复制代码

对于Windows

Java 层的1和2都映射到THREAD_PRIORITY_LOWEST,其他也类似,连续两个值分别映射到相同值上。

int os::java_to_os_priority[CriticalPriority + 1] = {
  THREAD_PRIORITY_IDLE,                         // 0  Entry should never be used
  THREAD_PRIORITY_LOWEST,                       // 1  MinPriority
  THREAD_PRIORITY_LOWEST,                       // 2
  THREAD_PRIORITY_BELOW_NORMAL,                 // 3
  THREAD_PRIORITY_BELOW_NORMAL,                 // 4
  THREAD_PRIORITY_NORMAL,                       // 5  NormPriority
  THREAD_PRIORITY_NORMAL,                       // 6
  THREAD_PRIORITY_ABOVE_NORMAL,                 // 7
  THREAD_PRIORITY_ABOVE_NORMAL,                 // 8
  THREAD_PRIORITY_HIGHEST,                      // 9  NearMaxPriority
  THREAD_PRIORITY_HIGHEST,                      // 10 MaxPriority
  THREAD_PRIORITY_HIGHEST                       // 11 CriticalPriority
};
复制代码

而 Windows 平台有如下值,可以看到并没有对全部值进行映射。

THREAD_MODE_BACKGROUND_BEGIN
THREAD_MODE_BACKGROUND_END
THREAD_PRIORITY_ABOVE_NORMAL
THREAD_PRIORITY_BELOW_NORMAL
THREAD_PRIORITY_HIGHEST
THREAD_PRIORITY_IDLE
THREAD_PRIORITY_LOWEST
THREAD_PRIORITY_NORMAL
THREAD_PRIORITY_TIME_CRITICAL
复制代码

OS线程优先级设置

前面说到 JVM 通过调用set_native_priority函数设置操作系统的线程优先级,这个函数会根据不同的操作系统做不同处理,这里看看 Linux 和 Windows 的情况。

对于Linux

调用系统函数setpriority来实现,成功返回 OS_OK。

OSReturn os::set_native_priority(Thread* thread, int newpri) {
  if (!UseThreadPriorities || ThreadPriorityPolicy == 0) return OS_OK;

  int ret = setpriority(PRIO_PROCESS, thread->osthread()->thread_id(), newpri);
  return (ret == 0) ? OS_OK : OS_ERR;
}
复制代码

对于Windows

调用系统函数SetThreadPriority来实现,成功返回 OS_OK。

OSReturn os::set_native_priority(Thread* thread, int priority) {
  if (!UseThreadPriorities) return OS_OK;
  bool ret = SetThreadPriority(thread->osthread()->thread_handle(), priority) != 0;
  return ret ? OS_OK : OS_ERR;
}
复制代码

————-推荐阅读————

我的2017文章汇总——机器学习篇

我的2017文章汇总——Java及中间件

我的2017文章汇总——深度学习篇

我的2017文章汇总——JDK源码篇

我的2017文章汇总——自然语言处理篇

我的2017文章汇总——Java并发篇

跟我交流,向我提问:

《从Java到JVM到OS线程的优先级》

公众号的菜单已分为“读书总结”、“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

为什么写《Tomcat内核设计剖析》

欢迎关注:

《从Java到JVM到OS线程的优先级》

    原文作者:JVM
    原文地址: https://juejin.im/post/5b3038cc6fb9a00e63253a79
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞