從Java到JVM到OS執行緒的優先順序

超人汪小建發表於2018-06-25

前言

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併發篇


跟我交流,向我提問:

這裡寫圖片描述

公眾號的選單已分為“讀書總結”、“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。

為什麼寫《Tomcat核心設計剖析》

歡迎關注:

這裡寫圖片描述

相關文章