從 Java 到 JVM 到 OS 線程的優先級
Java 的線程的調度機制由 JVM 實現,假如有若幹條線程,你想讓某些線程擁有更長的執行時間,或某些線程分配少點執行時間,這時就涉及“線程優先級”。
優先級別
Java 把線程優先級分成10個級別,線程被創建時如果沒有明確聲明則使用默認優先級,JVM 將根據每個線程的優先級分配執行時間的概率。有三個常量 Thread.MIN_PRIORITY 、 Thread.NORM_PRIORITY 、 Thread.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()); } }
}
setPriority方法
該方法用於設置優先級,邏輯為:
檢查是否有權限訪問該線程。
檢查優先級值的合法性,必須在 MIN_PRIORITY 和 MAX_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;
}
從 Java 到 JVM 到 OS 線程的優先級