程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 如何創建多線程,如何理解線程之間的優先級

如何創建多線程,如何理解線程之間的優先級

編輯:關於JAVA

問題

前面介紹了如何使用兩種方法來創建一個線程,此外,還發現程序中原來本身就隱含了一個特殊的線程——主線程。主線程是系統默認創建的,它的功能主要是產生新的線程和執行程序,更多的任務仍需要由用戶自己新建的線程來實現。因此往往一個程序需要創建多個線程,那麼如何創建多個用戶線程呢?這些用戶線程之間是否會平等地執行呢?如果線程間存在優先級,那麼應該如何正確地理解多線程的優先級問題呢?

解決思路

由於已經介紹了如何創建一個線程,那麼創建多線程自然是很容易的一件事情,只不過用不同的方法創建的多線程在實際應用方面多少有一些不同之處。下面會在具體步驟中通過實際的例子來演示這些不同。

此外,應該看到,當程序中有多個線程存在的時候,線程和線程之間的關系並非是平等的。例如,總有一些線程是CPU消耗密集型的,也就說該線程所對應的任務是緊迫的,因此需提高這些線程的優先級來保證這些線程能夠分得更多的時間片。這時肯定會出現高優先級的線程搶占低優先級線程的執行權的情況。這樣一來,很有可能出現低優先級的線程長時間得不到執行的問題。要解決這個問題,就要求在那些已經設置為高優先級的線程完成了任務以後,降低它們的優先級從而使其他線程也能夠得到執行。下面就介紹這些方法實現的具體步驟。

具體步驟

1 / 在程序中創建多線程

(1)通過擴展Thread類來創建多線程

假設一個影院有三個售票口,分別用於向兒童、成人和老人售票。影院為每個窗口放有100張電影票,分別是兒童票、成人票和老人票。三個窗口需要同時賣票,而現在只有一個售票員,這個售票員就相當於一個CPU,三個窗口就相當於三個線程。通過程序來看一看是如何創建這三個線程的。

// 例4.2.1  MutliThreadDemo.Java

class MutliThread extends Thread



private int ticket = 100;  // 每一個線程都擁有100張票

MutliThread(String name)

{

super(name);

}

public void run()

{

while(ticket>0)

System.out.println(ticket--+"  is saled by "

+currentThread().getName());

}

}

class MutliThreadDemo

{

public static void main(String[] args)

{

MutliThread m1 = new MutliThread("Window 1");

MutliThread m2 = new MutliThread("Window 2");

MutliThread m3 = new MutliThread("Window 3");

m1.start();

m2.start();

m3.start();



}

程序中定義一個線程類,它擴展了Thread類。利用擴展的線程類在MutliThreadDemo類的主方法中創建了三個線程對象,並通過start()方法分別將它們啟動。執行這個程序就可以看到如圖4.2.1所示的結果:

圖4.2.1  模擬三個窗口同時賣票的多線程舉例

從結果可以看到,每個線程分別對應100張電影票,之間並無任何關系,這就說明每個線程之間是平等的,沒有優先級關系,因此都有機會得到CPU的處理。但是結果顯示這三個線程並不是依次交替執行,而是在三個線程同時被執行的情況下,有的線程被分配時間片的機會多,票被提前賣完,而有的線程被分配時間片的機會比較少,票遲一些賣完。

可見,利用擴展Thread類創建的多個線程,雖然執行的是相同的代碼,但彼此相互獨立,且各自擁有自己的資源,互不干擾。

(2)通過實現Runnable接口來創建多線程

同樣是這個例子,能否用實現Runnable接口的方式來創建多線程以實現上述功能呢?看下面的程序,請注意和例4.2.1的區別。

// 例 4.2.2  MutliThreadDemo2.Java

class MutliThread implements Runnable



private int ticket = 100;

private String name;

MutliThread(String name)

{

this.name=name;

}

public void run()

{

while(ticket>0)

System.out.println(ticket--+"  is saled by "+name);

}

}

class MutliThreadDemo2

{

public static void main(String[] args)

{

MutliThread m1 = new MutliThread("Window 1");

MutliThread m2 = new MutliThread("Window 2");

MutliThread m3 = new MutliThread("Window 3");

Thread t1 = new Thread(m1);

Thread t2 = new Thread(m2);

Thread t3 = new Thread(m3);

t1.start();

t2.start();

t3.start();

}

}

該程序幾乎和例4.2.1完全一樣,只不過程序中MutliThread類實現了Runnable接口,主方法中三個線程是由這個實現了Runnable接口的類和Thread類共同創建的,最後也是通過start()方法將它們全部啟動。

由於這三個線程也是彼此獨立,各自擁有自己的資源,即100張電影票,因此程序輸出的結果和例4.2.1的結果大同小異。均是各自線程對自己的100張票進行單獨的處理,互不影響。

可見,只要現實的情況要求保證新建線程彼此相互獨立,各自擁有資源,且互不干擾,采用哪個方式來創建多線程都是可以的。因為這兩種方式創建的多線程程序能夠實現相同的功能。

通過實現Runnable接口來創建多線程程序的方法其實還可以用另一種方式來編寫,盡管功能相同,但卻能夠提供更多的選擇,這種方法如下所示。

// 例4.2.3  MutliThreadDemo3.Java

class MutliThread implements Runnable



private int ticket = 100;

Thread t;  

MutliThread(String name)

{

t = new Thread(this,name);     // 創建線程

}

public void run()

{

while(ticket>0)

System.out.println(ticket--+"  is saled by "+t.getName());

}

public void start()  // 自定義start()方法來調用Thread類的start()方法

{

t.start();

}

}

class MutliThreadDemo3

{

public static void main(String args[])

{

MutliThread m1 = new MutliThread("Window 1");

MutliThread m2 = new MutliThread("Window 2");

MutliThread m3 = new MutliThread("Window 3");

// 這裡調用的是MutliThread類自定義的start()方法而不是Thread類的start()方法

m1.start();  

m2.start();

m3.start();



}

可以看到,程序執行的順序是:創建MutliThread類的對象->初始化成員變量->調用構造函數->創建線程->調用MutliThread類的start()方法->啟動線程->調用MutliThread類的run()方法執行線程->所有線程結束->程序執行完畢。

使用上面的這些創建多線程的方式創建的線程彼此都是獨立的,它們都擁有各自的內存資源,互不干擾。但是現實中也存在這樣的情況,比如模擬一個火車站的售票系統,假如當日從A地發往B地的火車票只有100張,且允許所有窗口賣這100張票,那麼每一個窗口也相當於一個線程,但是這時和前面的例子不同之處就在於所有線程處理的資源是同一個資源,即100張車票。如果還用前面的方式來創建線程顯然是無法實現的,這種情況該怎樣處理呢?看下面這個程序,程序代碼如下所示:

// 例4.2.4  MutliThreadDemo4.Java

class MutliThread implements Runnable



private int ticket = 100;

public void run()

{

while(ticket>0)

System.out.println(ticket--+"  is saled by "

+Thread.currentThread().getName());

}

}

class MutliThreadDemo4

{

public static void main(String args[])

{

MutliThread m = new MutliThread();

Thread t1 = new Thread(m,"Windows 1");

Thread t2 = new Thread(m,"Windows 2");

Thread t3 = new Thread(m,"Windows 3");

t1.start();

t2.start();

t3.start();

}

}

整個程序和例4.2.2很相似,但是,在主程序中僅創建了一個MutliThread類的對象m,然後使用Thread類的Thread(Runnable target,String name)構造函數創建了三個線程,最後分別調用start()方法啟動這些線程。這和例4.2.2有什麼區別呢?

前者例4.2.2是先創建三個實現了Runnable接口類的對象,這些對象相當於在內存中分別創建了三個資源,然後為每個資源分別創建了一個線程,此時這三個線程各自擁有一個資源,且彼此獨立。而本例中僅創建一個實現了Runnable接口類的對象,因此也就僅創建了一個資源,隨後針對這一資源分別創建了三個線程,此時的三個線程彼此之間就有了一定的聯系,即要共同處理這同一個資源。盡管具有時間片分配的隨機性,但每一時刻只有一個線程在處理該資源,因此可以保證資源的一致性。正是基於這一點,該程序才實現了類似於火車站售票系統的功能。

編譯並運行程序,可以看到如圖4.2.2的結果:

圖4.2.2  共享同一個資源的多線程

圖中結果正如前面分析的那樣,程序在內存中僅創建了一個資源,而新建的三個線程都是基於訪問這同一資源的,並且由於每個線程上所運行的是相同的代碼,因此它們執行的功能也是相同的。

可見,如果現實問題中要求必須創建多個線程來執行同一任務,而且這多個線程之間還將共享同一個資源,那麼就可以使用實現Runnable接口的方式來創建多線程程序。而這一功能通過擴展Thread類是無法實現的,讀者想想看,為什麼?

實現Runnable接口相對於擴展Thread類來說,具有無可比擬的優勢。這種方式不僅有利於程序的健壯性,使代碼能夠被多個線程共享,而且代碼和數據資源相對獨立,從而特別適合多個具有相同代碼的線程去處理同一資源的情況。這樣一來,線程、代碼和數據資源三者有效分離,很好地體現了面向對象程序設計的思想。因此,幾乎所有的多線程程序都是通過實現Runnable接口的方式來完成的。

2 / 設置線程的優先級

雖然前面說線程是並發運行的。然而實際情況並非如此。對於多線程的程序,任務角色的不同使得每個線程的重要程度也不盡相同,如多個線程在等待獲得CPU時間片,往往希望優先級高的線程優先搶占到CPU並得以執行。此外,多個線程交替執行時,不同優先級決定了級別高的線程將得到CPU的次數多一些且時間長一些,這樣,高優先級的線程任務處理的效率明顯就會更高一些,從而滿足一些特殊的需要。那麼,線程的優先級是如何設置的呢?

Java的線程調度器決定了某一線程什麼時候該運行,該調度器采用的是一種簡單、固定的調度法,即固定優先級調度算法。這種算法是根據處於可運行狀態的線程的相對優先級來執行的。

Java中,線程的優先級是介於Thread.MIN_PRIORITY到Thread.MAX_PRIORITY這兩個常量之間的某個整數數值(介於1到10之間)。默認情況下,線程的優先級都是5,在Java中用NORM_PRIORITY來表示。其中,MIN_PRIORITY、MAX_PRIORITY和NORM_PRIORITY均是Thread類的靜態整型常量。

當利用某一線程又創建了一個新線程對象時,這個新線程將擁有與創建它的線程一樣的優先級。例如,主線程的優先級默認情況下是5,那麼利用主線程創建的新線程的優先級默認情況下也是5。

線程創建後,線程的優先級可以在需要的時候對其進行修改。修改時需要使用Thread類的setPriority()方法,該方法屬於Thread類,其語法格式為:

public final void setPriority(int newPriority)

這裡,newPriority的值必須在MIN_PRIORITY和MAX_PRIORITY之間,它的值可以是1到10之間的任意數字,也可以是MIN_PRIORITY等這些符號常量。

例如可以使用下面的形式來設置線程的優先級。

Thread.setPriority(Thread.MIN_PRIORITY);

當然,也可以通過調用Thread類的getPriority()方法來得到線程當前的優先級, 該方法也屬於Thread類,調用它將返回一個整數數值。其語法格式如下:

public final int getPriority()

由於這兩個方法都是final的,因此它們都無法被Thread類的子類所覆蓋。在任何時刻,如果有多條線程等待運行,系統將選擇優先級最高的可運行線程運行。只有當它停止、自動放棄、或由於某種原因成為非運行狀態的線程時,其他優先級的線程才能運行。如果兩個線程具有相同的優先級,則它們將被交替地運行。

下面通過一個例子來說明設置線程的優先級是如何影響到線程在程序中所表現的效果的。在這個例子中利用主線程創建了兩個用戶線程,通過循環來統計兩個線程執行的次數。如果它們的優先級相同,則當循環次數很大時,兩個線程執行的次數應該非常相近。如果它們的優先級不同,則當循環N次之後,統計的結果會顯示優先級大的線程執行的次數比優先級小的線程執行的次數大很多。程序代碼如下所示:

// 例4.2.5  MutliThreadDemo5.Java

class NewThread extends Thread

{

private int count;

private boolean isPass = true;   // 定義一個標志,用來終止循環

NewThread( String name )

{

super(name);

}

public void run()

{

while(isPass)  // isPass為假時將中止循環,否則count不斷的加1

{

count++;

}

}

public int result()  // 返回count的值

{

return count;

}

public void stopThread()   // 中止線程

{

isPass = false;

}

}

class MutliThreadDemo5

{

public static void main( String[] args)

{

NewThread t1 = new NewThread("Thread 1"); 

NewThread t2 = new NewThread("Thread 2");

t1.setPriority(Thread.NORM_PRIORITY-3); //設置優先級為2

t2.setPriority(Thread.NORM_PRIORITY+3); //設置優先級為8

t1.start();    // 啟動線程t1

t2.start();    // 啟動線程t2

try

{

Thread.sleep(500); // 主線程睡眠500毫秒

}catch(InterruptedException e){

System.out.println(e.getMessage());

}

System.out.println("Thread 1:Priority is "+t1.getPriority()

+" Result of Count is: "+t1.result());

System.out.println("Thread 2:Priority is "+t2.getPriority()

+" Result of Count is: "+t2.result());

t1.setPriority(Thread.MAX_PRIORITY);  //重新設置t1的優先級為最大

try{

Thread.sleep(500); // 主線程睡眠500毫秒

}catch(InterruptedException e){

System.out.println(e.getMessage());

}

t1.stopThread();

t2.stopThread();

System.out.println("After the priority of Thread 1 is changed: ");

System.out.println("Thread 1:Priority is "+t1.getPriority()

+" Result of Count is: "+t1.result());

System.out.println("Thread 2:Priority is "+t2.getPriority()

+" Result of Count is: "+t2.result());

}

}


 

程序執行後顯示的結果如圖4.2.3所示:

圖 4.2.3  設置線程的優先級

由顯示的結果可以看到,開始的時候,Thread1的優先級遠遠低於Thread2的優先級,因此Thread2執行的次數比Thread1多很多。而重新設置了Thread1的優先級,使之成為最大優先級之後,Thread1執行的次數大大增加,而此時的Thread2由於優先級沒有Thread1高,其執行的次數增加量變化反而不大。

並不是在所有系統中運行Java程序時都采用時間片策略調度線程,所以一個線程在空閒時應該主動放棄CPU,以使其他同優先級和低優先級的線程得到執行。如本例中對主線程就使用了sleep()方法。其他方法讀者可以參考本節後面的相關問題。

專家說明

本小節中介紹了如何創建多線程的程序,還有如何編寫使用多線程的程序訪問同一資源的方法,同時指出了在創建多線程程序中使用實現Runnable接口創建多線程程序在其他方面的優勢。因此應盡量使用Runnable接口來創建多線程以便於程序功能的擴展。另外,還介紹了對於那些緊迫的任務、需要大量消耗CPU時間的線程,如何設置其優先級來保證任務的實現。雖然不能具體精確地控制線程,但是針對那些僅通過設置或改變線程的優先級就可以改善程序性能的現實問題,利用本小節中對線程優先級的設置方法還是很有效果的。

專家指點

最後,要特別指出的是:Java 雖然支持 10 個優先級,但基層操作系統支持的優   先級可能要少得多,這樣就有可能造成一些混亂。因此,只能將優先級作為一種很粗略的工具使用,最後的控制可以通過恰當地使用Thread類的 yield()方法來完成。一般情況下,請不要依靠線程優先級來控制線程的狀態。關於yIEld()方法的說明可以參考相關問題。

相關問題

在協作式模型中,是否能保證線程正常放棄處理器,不掠奪其他線程的執行時間,則完全取決於程序員。可以通過調用Thread類的yIEld()方法,使之能夠將當前的線程從處理器中移出並重新放回到准備就緒隊列中。另一個途徑則是調用 sleep()方法,使線程在 sleep()方法指定的時間間隔內進入睡眠狀態,從而放棄處理器。

sleep()方法的使用前面已經介紹過,在此不再贅述。下面簡單的介紹一下yIEld()方法,它是Thread類的靜態成員,語法格式如下:

public static void yIEld()

此方法的功能是可以引起當前正在執行的線程對象臨時性的暫停執行,而使其他線程得到執行。例如,當線程需要放棄某個稀有的資源(如數據庫連接或網絡端口)時,它可以通過調用yIEld()方法來臨時降低自己的優先級,以便程序中其他低優先級的線程能夠運行,並得到這個稀有資源。

將這個方法隨意放在代碼的某個地方,並不能夠保證線程正常工作。例如,如果線程正擁有一個鎖(因為它在一個同步方法或代碼塊中,後面的小節中會講到有關同步的問題,現在讀者對它有一個了解即可),則當它調用 yield() 時由於無法釋放這個鎖,就意味著即使這個線程已經被掛起,等待這個鎖釋放的其他線程依然不能繼續運行。為了緩解這個問題,最好不在同步方法中調用 yield()方法,否則將那些需要同步的代碼包在一個同步塊中,裡面不含有非同步的方法,並且在這些同步代碼塊之外才調用 yIEld(),這樣也可以解決這個問題。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved