java多線程編程

二:在Java中實現多線程
  咱們不妨設想,爲了建立一個新的線程,咱們須要作些什麼?很顯然,咱們必須指明這個線程所要執行的代碼,而這就是在Java中實現多線程咱們所須要作的一切!
  做爲一個徹底面向對象的語言,Java提供了類 java.lang.Thread 來方便多線程編程,這個類提供了大量的方法來方便咱們控制本身的各個線程。
  那麼如何提供給 Java 咱們要線程執行的代碼呢?讓咱們來看一看 Thread 類。Thread 類最重要的方法是 run() ,它爲Thread 類的方法 start() 所調用,提供咱們的線程所要執行的代碼。爲了指定咱們本身的代碼,只須要覆蓋它!
方法一:繼承 Thread 類,重寫方法 run(),咱們在建立的 Thread 類的子類中重寫 run() ,加入線程所要執行的代碼便可。下面是一個例子:
public class TwoThread extends Thread {
    public void run() {
        for ( int i = 0; i < 10; i++ ) {
            System.out.println("New thread");
        }
    }
    public static void main(String[] args) {
        TwoThread tt = new TwoThread();
        tt.start();
        for ( int i = 0; i < 10; i++ ) {
            System.out.println("Main thread");
        }
    }
}
  這種方法簡單明瞭,符合你們的習慣,可是,它也有一個很大的缺點,那就是若是咱們的類已經從一個類繼承,則沒法再繼承 Thread 類。
方法二:實現 Runnable 接口
Runnable 接口只有一個方法 run(),咱們聲明本身的類實現 Runnable 接口並提供這一方法,將咱們的線程代碼寫入其中,就完成了這一部分的任務。可是 Runnable 接口並無任何對線程的支持,咱們還必須建立 Thread 類的實例,這一點經過 Thread 類的構造函數public Thread(Runnable target);來實現。下面是一個例子:
public class MyThread implements Runnable {
int count=1, number;
public MyThread(int num) {
number = num;
System.out.println("建立線程 " + number);
}
public void run() {
while(true) {
System.out.println("線程 " + number + ":計數 " + count);
if(++count== 6) return;
}
}
public static void main(String args[]) {
for(int i = 0; i < 5; i++)
new Thread(new MyThread(i+1)).start();
}
}
使用 Runnable 接口來實現多線程使得咱們可以在一個類中包容全部的代碼,有利於封裝下面讓咱們一塊兒來研究一下多線程使用中的一些問題。
 
三:線程的四種狀態
1. 新狀態:線程已被建立但還沒有執行(start() 還沒有被調用)。
2. 可執行狀態:線程能夠執行,雖然不必定正在執行。CPU 時間隨時可能被分配給該線程,從而使得它執行。
3. 阻塞狀態:線程不會被分配 CPU 時間,沒法執行;可能阻塞於I/O,或者阻塞於同步鎖。
4. 死亡狀態:正常狀況下run() 返回使得線程死亡。調用 stop()或 destroy() 亦有一樣效果,可是不被推薦,前者會產生異常,後者是強制終止,不會釋放鎖。
 
四:線程的優先級
  線程的優先級表明該線程的重要程度,當有多個線程同時處於可執行狀態並等待得到 CPU 時間時,線程調度系統根據各個線程的優先級來決定給誰分配 CPU 時間,優先級高的線程有更大的機會得到 CPU 時間,優先級低的線程也不是沒有機會,只是機會要小一些罷了。
你能夠調用 Thread 類的方法 getPriority() 和 setPriority()來存取線程的優先級,線程的優先級界於1(MIN_PRIORITY)和10(MAX_PRIORITY)之間,缺省是5(NORM_PRIORITY)。

五:線程的同步
因爲同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。Java語言提供了專門機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問。
咱們只需針對方法提出一套機制,這套機制就是 synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。
1. synchronized 方法:經過在方法聲明中加入synchronized關鍵字來聲明 synchronized 方法。synchronized 方法控制對類成員變量的訪問:每一個類實例對應一把鎖,每一個 synchronized 方法都必須得到調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能得到該鎖,從新進入可執行狀態。這種機制確保了同一時刻對於每個類實例,其全部聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(由於至多隻有一個可以得到該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要全部可能訪問類成員變量的方法均被聲明爲 synchronized)。
Java 中,不光是類實例,每個類也對應一把鎖,這樣咱們也可將類的靜態成員函數聲明爲 synchronized,以控制其對類的靜態成員變量的訪問。
synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率,典型地,若將線程類的方法run()聲明爲synchronized ,因爲在線程的整個生命期內它一直在運行,所以將致使它對本類任何synchronized方法的調用都永遠不會成功。
 
2. synchronized 塊:經過 synchronized關鍵字來聲明synchronized 塊。語法以下:
synchronized(syncObject) {
//容許訪問控制的代碼
}
synchronized 塊是這樣一個代碼塊,其中的代碼必須得到對象 syncObject 的鎖方能執行,具體機制同前所述。因爲能夠針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。
 
六:線程的阻塞
爲了解決對共享存儲區的訪問衝突,Java 引入了同步機制,如今讓咱們來考察多個線程對共享資源的訪問,顯然同步機制已經不夠了,由於在任意時刻所要求的資源不必定已經準備好了被訪問,反過來,同一時刻準備好了的資源也可能不止一個。爲了解決這種狀況下的訪問控制問題,Java 引入了對阻塞機制的支持。
阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒)。Java 提供了大量方法來支持阻塞,下面讓對它們逐一分析。
1. sleep()方法:sleep()容許指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得CPU 時間,指定的時間一過,線程從新進入可執行狀態。
典型地,sleep() 被用在等待某個資源就緒的情形:測試發現條件不知足後,讓線程阻塞一段時間後從新測試,直到條件知足爲止。
 
2. yield() 方法:yield() 使得線程放棄當前分得的 CPU 時間,可是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另外一個線程。
3. wait() 和 notify() 方法:兩個方法配套使用,wait() 使得線程進入阻塞狀態,它有兩種形式,一種容許指定以毫秒爲單位的一段時間做爲參數,另外一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程從新進入可執行狀態,後者則必須對應的 notify() 被調用。
wait() 和 notify() 方法的上述特性決定了它們常常和synchronized 方法或塊一塊兒使用,將它們和操做系統的進程間通訊機制做一個比較就會發現它們的類似性:synchronized方法或塊提供了相似於操做系統原語的功能,它們的結合用於解決各類複雜的線程間通訊問題。
關於 wait() 和 notify() 方法最後再說明兩點:
第一:調用 notify() 方法致使解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,咱們沒法預料哪個線程將會被選擇,因此編程時要特別當心,避免因這種不肯定性而產生問題。
第二:除了 notify(),還有一個方法 notifyAll() 也可起到相似做用,惟一的區別在於,調用 notifyAll() 方法將把因調用該對象的wait()方法而阻塞的全部線程一次性所有解除阻塞。固然,只有得到鎖的那一個線程才能進入可執行狀態。
談到阻塞,就不能不談一談死鎖,略一分析就能發現,suspend() 方法和不指定超時期限的 wait() 方法的調用均可能產生死鎖。遺憾的是,Java 並不在語言級別上支持死鎖的避免,咱們在編程中必須當心地避免死鎖。
    以上咱們對 Java 中實現線程阻塞的各類方法做了一番分析,咱們重點分析了 wait() 和 notify() 方法,由於它們的功能最強大,使用也最靈活,可是這也致使了它們的效率較低,較容易出錯。實際使用中咱們應該靈活使用各類方法,以便更好地達到咱們的目的。
    還有一個方法比較有用,Thread的join()方法可用於讓當前線程阻塞,以等待特定線程的消亡。
 
七: 一個應用:線程池
1.背景
諸如 Web 服務器、數據庫服務器、文件服務器或郵件服務器之類的許多服務器應用程序都面向處理來自某些遠程來源的大量短小的任務。 服務器應用程序中常常出現的狀況是:單個任務處理的時間很短而請求的數目倒是巨大的。
2.解決方法
構建服務器應用程序的一個過於簡單的模型應該是:每當一個請求到達就建立一個新線程,而後在新線程中爲請求服務。
那麼這種方法的嚴重不足就很明顯。每一個請求對應一個線程(thread-per-request)方法的不足之一是:爲每一個請求建立一個新線程的開銷很大 ;在一個 JVM 裏建立太多的線程可能會致使系統因爲過分消耗內存而用完內存或「切換過分」。爲了防止資源不足,服務器應用程序須要一些辦法來限制任何給定時刻處理的請求數目。
咱們能夠實現一個線程池類,其中客戶機類等待一個可用線程、將任務傳遞給該線程以便執行、而後在任務完成時將線程歸還給池,
通常一個簡單線程池至少包含下列組成部分:
      線程池管理器(ThreadPoolManager):用於建立並管理線程池
      工做線程(WorkThread): 線程池中線程
      任務接口(Task):每一個任務必須實現的接口,以供工做線程調度任務的執行。
      任務隊列:用於存放沒有處理的任務。提供一種緩衝機制。
相關文章
相關標籤/搜索