讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議121:線程優先級只使用三個等級 >

建議121:線程優先級只使用三個等級

線程的優先級(Priority)決定了線程獲得CPU運行的機會,優先級越高獲得的運行機會越大,優先級越低獲得的機會越小。Java的線程有10個級別(準確地說是11個級別,級別為0的線程是JVM的,應用程序不能設置該級別),那是不是說級別是10的線程肯定比級別為9的線程先運行呢?我們來看如下一個多線程類:


class TestThread implements Runnable{

//啟動線程

public void start(int_priority){

Thread t=new Thread(this);

//設置線程優先級

t.setPriority(_priority);

t.start();

}

@Override

public void run(){

//消耗CPU的計算,性能差的機器,請修改循環限制

for(int i=0;i<100000;i++){

Math.hypot(Math.pow(924526789,i),Math.cos(i));

}

//輸出線程優先級

System.out.println(\"Priority:\"+Thread.currentThread().getPriority());

}

}


該多線程類實現了Runnable接口,實現了run方法,注意在run方法中有一個比較佔用CPU的計算,該計算毫無意義,只是為了保證一個線程盡可能多地消耗CPU資源,目的是為了觀察CPU繁忙時不同優先級線程的執行順序。需要說明的是,如果此處使用了Thread.sleep()方法,則不能體現出線程優先級的本質了,因為CPU並不繁忙,線程調度不會遵循優先級順序來進行調度。

客戶端的代碼如下:


public static void main(Stringargs){

//啟動20個不同優先級的線程

for(int i=0;i<20;i++){

new TestThread().start(i%10+1);

}

}


這裡創建了20個線程,每個線程在運行時都耗盡了CPU資源,因為優先級不同,線程調度應該最先處理優先級最高的,然後處理優先級最低的,也就是先執行2個優先級為10的線程,然後執行2個優先為9的線程,2個優先級為8的線程……但是結果卻並不是這樣的。


Priority:10

Priority:9

Priority:10

Priority:9

Priority:7

Priority:7

Priority:8

Priority:8

Priority:5

Priority:5

Priority:6

Priority:6

Priority:4

Priority:4

Priority:3

Priority:3

Priority:1

Priority:1

Priority:2

Priority:2


println方法雖然有輸出損耗,可能會影響到輸出結果,但是不管運行多少次,你都會發現兩個不爭的事實:

(1)並不是嚴格遵照線程優先級別來執行的

比如線程優先級為9的線程可能比優先級為10的線程先執行,優先級為1的線程可能比優先級為2的線程先執行,但很少會出現優先級為2的線程比優先級為10的線程先執行(這裡用了一個詞「很少」,是說確實有可能出現,只是幾率非常低,因為優先級只是表示線程獲得CPU運行的機會,並不代表強制的排序號)。

(2)優先級差別越大,運行機會差別越明顯

比如優先級為10的線程通常會比優先級為2的線程先執行,但是優先級為6的線程和優先級為5的線程差別就不太明顯了,執行多次,你會發現有不同的順序。

這兩個現象是線程優先級的一個重要表現,之所以會出現這種情況,是因為線程運行是需要獲得CPU資源的,那誰能決定哪個線程先獲得哪個線程後獲得呢?這是依照操作系統設定的線程優先級來分配的,也就是說,每個線程要運行,需要操作系統分配優先級和CPU資源,對於Java來說,JVM調用操作系統的接口設置優先級,比如Windows操作系統是通過調用SetThreadPriority函數來設置的,問題來了:不同的操作系統線程優先級都相同嗎?

這是個好問題。事實上,不同的操作系統線程優先級是不相同的,Windows有7個優先級,Linux有140個優先級,Freebsd則有255個(此處指的是優先級總數,不同操作系統有不同的分類,如中斷級線程、操作系統級等,各個操作系統具體用戶可用的線程數量也不相同)。Java是跨平台的系統,需要把這個10個優先級映射成不同操作系統的優先級,於是界定了Java的優先級只是代表搶佔CPU的機會大小,優先級越高,搶佔CPU的機會越大,被優先執行的可能性越高,優先級相差不大,則搶佔CPU的機會差別也不大,這就是導致了優先級為9的線程可能比優先級為10的線程先運行。

Java的締造者們也察覺到了線程優先問題,於是在Thread類中設置了三個優先級,此意就是告訴開發者,建議使用優先級常量,而不是1到10隨機的數字。常量代碼如下:


public class Thread implements Runnable{

//最低優先級

public final static int MIN_PRIORITY=1;

//普通優先級,默認值

public final static int NORM_PRIORITY=5;

//最高優先級

public final static int MAX_PRIORITY=10;

}


在編碼時直接使用這些優先級常量,可以說在大部分情況下MAX_PRIORITY的線程會比NORM_PRIORITY的線程先運行,但是不能認為必然會先運行,不能把這個優先級做為核心業務的必然條件,Java無法保證優先級高肯定會先執行,只能保證高優先級有更多的執行機會。因此,建議在開發時只使用此三類優先級,沒有必要使用其他7個數字,這樣也可以保證在不同的操作系統上優先級的表現基本相同。

明白了這個問題,那可能有讀者要問了:如果優先級相同呢?這很好辦,也是由操作系統決定的,基本上是按照FIFO原則(先入先出,First Input First Output),但也是不能完全保證。

注意 線程優先級推薦使用MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY三個級別,不建議使用其他7個數字。