了解Java執行緒優先順序,更要知道對應作業系統的優先順序,不然會踩坑
- 2019 年 10 月 3 日
- 筆記
Java 多執行緒系列第 6 篇。
這篇我們來看看 Java 執行緒的優先順序。
Java 執行緒優先順序
Thread
類中,使用如下屬性來代表優先順序。
private int priority;
我們可以通過 setPriority(int newPriority)
來設置新的優先順序,通過 getPriority()
來獲取執行緒的優先順序。
有些資料通過下面的例子就得出了一個結論:Java 執行緒默認優先順序是 5。
public static void main(String[] args) { Thread thread = new Thread(); System.out.println(thread.getPriority()); } // 列印結果:5
其實這是大錯特錯的,只是看到了表面,看看下面的例子,我們把當前執行緒的優先順序改為 4,發現子執行緒 thread 的優先順序也是 4。
public static void main(String[] args) { Thread.currentThread().setPriority(4); Thread thread = new Thread(); System.out.println(thread.getPriority()); } // 列印結果:4
這啪啪啪打臉了,如果是執行緒默認優先順序是 5,我們新創建的 thread 執行緒,沒設置優先順序,理應是 5,但實際是 4。我們看看 Thread
初始化 priority
的源程式碼。
Thread parent = currentThread(); this.priority = parent.getPriority();
原來,執行緒默認的優先順序是繼承父執行緒的優先順序,上面例子我們把父執行緒的優先順序設置為 4,所以導致子執行緒的優先順序也變成 4。
嚴謹一點說,子執行緒默認優先順序和父執行緒一樣,Java 主執行緒默認的優先順序是 5。
Java 中定義了 3 種優先順序,分別是最低優先順序(1)
、正常優先順序(5)
、最高優先順序(10)
,程式碼如下所示。Java 優先順序範圍是 [1, 10],設置其他數字的優先順序都會拋出 IllegalArgumentException
異常。
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
接下來說說執行緒優先順序的作用。先看下面程式碼,程式碼邏輯是創建了 3000 個執行緒,分別是: 1000 個優先順序為 1 的執行緒, 1000 個優先順序為 5 的執行緒,1000 個優先順序為 10 的執行緒。用 minTimes
來記錄 1000 個 MIN_PRIORITY
執行緒運行時時間戳之和,用 normTimes
來記錄 1000 個 NORM_PRIORITY
執行緒運行時時間戳之和,用 maxTimes
來記錄 1000 個 MAX_PRIORITY
執行緒運行時時間戳之和。通過統計每個優先順序的運行的時間戳之和,值越小代表的就是越優先執行。我們運行看看。
public class TestPriority { static AtomicLong minTimes = new AtomicLong(0); static AtomicLong normTimes = new AtomicLong(0); static AtomicLong maxTimes = new AtomicLong(0); public static void main(String[] args) { List<MyThread> minThreadList = new ArrayList<>(); List<MyThread> normThreadList = new ArrayList<>(); List<MyThread> maxThreadList = new ArrayList<>(); int count = 1000; for (int i = 0; i < count; i++) { MyThread myThread = new MyThread("min----" + i); myThread.setPriority(Thread.MIN_PRIORITY); minThreadList.add(myThread); } for (int i = 0; i < count; i++) { MyThread myThread = new MyThread("norm---" + i); myThread.setPriority(Thread.NORM_PRIORITY); normThreadList.add(myThread); } for (int i = 0; i < count; i++) { MyThread myThread = new MyThread("max----" + i); myThread.setPriority(Thread.MAX_PRIORITY); maxThreadList.add(myThread); } for (int i = 0; i < count; i++) { maxThreadList.get(i).start(); normThreadList.get(i).start(); minThreadList.get(i).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("maxPriority 統計:" + maxTimes.get()); System.out.println("normPriority 統計:" + normTimes.get()); System.out.println("minPriority 統計:" + minTimes.get()); System.out.println("普通優先順序與最高優先順序相差時間:" + (normTimes.get() - maxTimes.get()) + "ms"); System.out.println("最低優先順序與普通優先順序相差時間:" + (minTimes.get() - normTimes.get()) + "ms"); } static class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println(this.getName() + " priority: " + this.getPriority()); switch (this.getPriority()) { case Thread.MAX_PRIORITY : maxTimes.getAndAdd(System.currentTimeMillis()); break; case Thread.NORM_PRIORITY : normTimes.getAndAdd(System.currentTimeMillis()); break; case Thread.MIN_PRIORITY : minTimes.getAndAdd(System.currentTimeMillis()); break; default: break; } } } }
執行結果如下:
# 第一部分 max----0 priority: 10 norm---0 priority: 5 max----1 priority: 10 max----2 priority: 10 norm---2 priority: 5 min----4 priority: 1 ....... max----899 priority: 10 min----912 priority: 1 min----847 priority: 5 min----883 priority: 1 # 第二部分 maxPriority 統計:1568986695523243 normPriority 統計:1568986695526080 minPriority 統計:1568986695545414 普通優先順序與最高優先順序相差時間:2837ms 最低優先順序與普通優先順序相差時間:19334ms
我們一起來分析一下結果。先看看第一部分,最開始執行的執行緒高優先順序、普通優先順序、低優先順序都有,最後執行的執行緒也都有各個優先順序的,這說明了:優先順序高的執行緒不代表一定比優先順序低的執行緒優先執行。也可以換另一種說法:程式碼執行順序跟執行緒的優先順序無關。看看第二部分的結果,我們可以發現最高優先順序的 1000 個執行緒執行時間戳之和最小,而最低優先順序的 1000 個執行緒執行時間戳之和最大,因此可以得知:一批高優先順序的執行緒會比一批低優先順序的執行緒優先執行,即高優先順序的執行緒大概率比低優先的執行緒優先獲得 CPU 資源。
各作業系統中真有 10 個執行緒等級么?
Java 作為跨平台語言,執行緒有 10 個等級,但是映射到不同作業系統的執行緒優先順序值不一樣。接下來教大家怎麼在 OpenJDK
源碼中查各個作業系統中執行緒優先順序映射的值。
- 看到 Thread 源程式碼,設置執行緒優先順序最終調用了本地方法
setPriority0()
;
private native void setPriority0(int newPriority);
- 接著我們在 OpenJDK 的
Thread.c
程式碼中找到setPriority0()
對應的方法JVM_SetThreadPriority
;
static JNINativeMethod methods[] = { ... {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority}, ... };
- 我們根據
JVM_SetThreadPriority
找到 jvm.cpp 中對應的程式碼段;
JVM_ENTRY(void, JVM_SetThreadPriority(JNIEnv* env, jobject jthread, jint prio)) JVMWrapper("JVM_SetThreadPriority"); // Ensure that the C++ Thread and OSThread structures aren't freed before we operate 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 not yet started; priority pushed down when it is Thread::set_priority(thr, (ThreadPriority)prio); } JVM_END
- 根據第 3 步中的程式碼,我們可以發現關鍵是
java_lang_Thread::set_Priority()
這段程式碼,繼續找 thread.cpp 程式碼中的set_Priority()
方法;
void Thread::set_priority(Thread* thread, ThreadPriority priority) { trace("set priority", thread); debug_only(check_for_dangling_thread_pointer(thread);) // Can return an error! (void)os::set_priority(thread, priority); }
- 發現上面程式碼最終調用的是
os::set_priority()
,接著繼續找出 os.cpp 的set_priority()
方法;
OSReturn os::set_priority(Thread* thread, ThreadPriority p) { #ifdef ASSERT if (!(!thread->is_Java_thread() || Thread::current() == thread || Threads_lock->owned_by_self() || thread->is_Compiler_thread() )) { assert(false, "possibility of dangling Thread pointer"); } #endif 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_to_os_priority[p]
,接下來就是找各個作業系統下的該數組的值。比如下面是 Linux 系統的優先順序值。
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 };
好了,大家應該知道怎麼找出 Java 執行緒優先順序 [1,10] 一一對應各個作業系統中的優先順序值。下面給大家統計一下。
Java 執行緒優先順序 | Linux | Windows | Apple | Bsd | Solaris |
---|---|---|---|---|---|
1 | 4 | THREAD_PRIORITY_LOWEST(-2) | 27 | 0 | 0 |
2 | 3 | THREAD_PRIORITY_LOWEST(-2) | 28 | 3 | 32 |
3 | 2 | THREAD_PRIORITY_BELOW_NORMAL(-1) | 29 | 6 | 64 |
4 | 1 | THREAD_PRIORITY_BELOW_NORMAL(-1) | 30 | 10 | 96 |
5 | 0 | THREAD_PRIORITY_NORMAL(0) | 31 | 15 | 127 |
6 | -1 | THREAD_PRIORITY_NORMAL(0) | 32 | 18 | 127 |
7 | -2 | THREAD_PRIORITY_ABOVE_NORMAL(1) | 33 | 21 | 127 |
8 | -3 | THREAD_PRIORITY_ABOVE_NORMAL(1) | 34 | 25 | 127 |
9 | -4 | THREAD_PRIORITY_HIGHEST(2) | 35 | 28 | 127 |
10 | -5 | THREAD_PRIORITY_HIGHEST(2) | 36 | 31 | 127 |
Windows 系統的在 OpenJDK 源碼中只找到上面的常量,值是通過微軟提供的函數介面文檔查到的,鏈接在這:setthreadpriority
我們從這個表格中也可以發現一些問題,即使我們在 Java 程式碼中設置了比較高的優先順序,其實映射到作業系統的執行緒裡面,並不一定比設置了低優先順序的執行緒高,很有可能是相同的優先順序。看看 Solaris 作業系統 這個極端的例子,優先順序 5 到 10 映射的是相同的執行緒等級。
回頭想想上面的例子為什麼 3000 個執行緒,MAX_PRIORITY
優先順序的 1000 個執行緒會優先執行呢?因為我們的 3 個優先順序分別映射到 Windows 作業系統執行緒的 3 個不同的等級,所以才會生效。假設將 1、5、10 改成 5、6、7,運行結果那就不大一樣了。
最後記住:切莫把執行緒優先順序當做銀彈,優先順序高的執行緒不一定比優先順序低的執行緒優先執行。
這篇執行緒優先順序文章也告段落了,朋友們看完覺得有用麻煩幫點個在看
,推薦給身邊朋友看看,原創不易。
推薦閱讀
了解Java執行緒優先順序,更要知道對應作業系統的優先順序,不然會踩坑
後台回復『設計模式』可以獲取《一故事一設計模式》電子書
覺得文章有用幫忙轉發&點贊,多謝朋友們!