了解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 源碼中查各個作業系統中執行緒優先順序映射的值。

  1. 看到 Thread 源程式碼,設置執行緒優先順序最終調用了本地方法 setPriority0()
private native void setPriority0(int newPriority);
  1. 接著我們在 OpenJDKThread.c 程式碼中找到 setPriority0() 對應的方法 JVM_SetThreadPriority
static JNINativeMethod methods[] = {      ...      {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},      ...  };
  1. 我們根據 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
  1. 根據第 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);  }
  1. 發現上面程式碼最終調用的是 os::set_priority(),接著繼續找出 os.cppset_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;    }  }
  1. 終於發現了最終轉換為各作業系統的優先順序程式碼 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執行緒優先順序,更要知道對應作業系統的優先順序,不然會踩坑

執行緒最最基礎的知識

老闆叫你別阻塞了

吃個快餐都能學到串列、並行、並發

泡一杯茶,學一學同非同步

進程知多少?

設計模式看了又忘,忘了又看?

後台回復『設計模式』可以獲取《一故事一設計模式》電子書

覺得文章有用幫忙轉發&點贊,多謝朋友們!

LieBrother