NO IMAGE

 

本文轉載:http://blog.csdn.net/u0fly/article/details/6386591

1、這兩個方法來自不同的類分別是Thread和Object

  2、最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他執行緒可以使用同步控制塊或者方法。

  3、wait,notify和notifyAll只能在同步控制方法或者同步控制塊裡面使用,而sleep可以在

  任何地方使用(使用範圍)

  synchronized(x){

  x.notify()

  //或者wait()

  }

  4、sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

  擴充閱讀:

  java 執行緒中的sleep和wait有一個共同作用,停止當前執行緒任務執行,但他們存在一定的不同,首先我們先看sleep中的建構函式

  sleep(long millis)           Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

  sleep(long millis, int nanos)          Causes the currently executing thread to sleep (cease execution) for the specified number of milliseconds plus the specified number of nanoseconds, subject to the precision and accuracy of system timers and schedulers.

  sleep方法屬於Thread類中方法,表示讓一個執行緒進入睡眠狀態,等待一定的時間之後,自動醒來進入到可執行狀態,不會馬上進入執行狀態,因為執行緒排程機制恢復執行緒的執行也需要時間,一個執行緒物件呼叫了sleep方法之後,並不會釋放他所持有的所有物件鎖,所以也就不會影響其他程序物件的執行。但在sleep的過程中過程中有可能被其他物件呼叫它的interrupt(),產生InterruptedException異常,如果你的程式不捕獲這個異常,執行緒就會異常終止,進入TERMINATED狀態,如果你的程式捕獲了這個異常,那麼程式就會繼續執行catch語句塊(可能還有finally語句塊)以及以後的程式碼。

  注意sleep()方法是一個靜態方法,也就是說他只對當前物件有效,通過t.sleep()讓t物件進入sleep,這樣的做法是錯誤的,它只會是使當前執行緒被sleep 而不是t執行緒

  wait方法

  void wait(long timeout)

  Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.

  void wait(long timeout, int nanos)

  Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.

  wait屬於Object的成員方法,一旦一個物件呼叫了wait方法,必須要採用notify()和notifyAll()方法喚醒該程序;如果執行緒擁有某個或某些物件的同步鎖,那麼在呼叫了wait()後,這個執行緒就會釋放它持有的所有同步資源,而不限於這個被呼叫了wait()方法的物件。wait()方法也同樣會在wait的過程中有可能被其他物件呼叫interrupt()方法而產生

  InterruptedException,效果以及處理方式同sleep()方法

  追加內容:

  Collection是個java.util下的介面,它是各種集合結構的父介面。

  Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。

  Collection 層次結構中的根介面。Collection 表示一組物件,這些物件也稱為 collection的元素。一些 collection 允許有重複的元素,而另一些則不允許。一些 collection 是有序的,而另一些則是無序的。JDK 不提供此介面的任何直接 實現:它提供更具體的子介面(如 Set 和 List)實現。此介面通常用來傳遞 collection,並在需要最大普遍性的地方操作這些 collection。

  collections 此類完全由在 collection 上進行操作或返回 collection 的靜態方法組成。它包含在 collection 上操作的多型演算法,即“包裝器”,包裝器返回由指定 collection 支援的新 collection,以及少數其他內容。 如果為此類的方法所提供的 collection 或類物件為 null,則這些方法都會丟擲 NullPointerException。

 

java多執行緒:

 

執行緒或者說多執行緒,是我們處理多工的強大工具。執行緒和程序是不同的,每個程序都是一個獨立執行的程式,擁有自己的變數,且不同程序間的變數不能共享;而執行緒是執行在程序內部的,每個正在執行的程序至少有一個執行緒,而且不同的執行緒之間可以在程序範圍內共享資料。也就是說程序有自己獨立的儲存空間,而執行緒是和它所屬的程序內的其他執行緒共享一個儲存空間。執行緒的使用可以使我們能夠並行地處理一些事情。執行緒通過並行的處理給使用者帶來更好的使用體驗,比如你使用的郵件系統(outlook、Thunderbird、foxmail等),你當然不希望它們在收取新郵件的時候,導致你連已經收下來的郵件都無法閱讀,而只能等待收取郵件操作執行完畢。這正是執行緒的意義所在。 

實現執行緒的方式 

實現執行緒的方式有兩種: 

  1. 繼承java.lang.Thread,並重寫它的run()方法,將執行緒的執行主體放入其中。
  2. 實現java.lang.Runnable介面,實現它的run()方法,並將執行緒的執行主體放入其中。

這是繼承Thread類實現執行緒的示例: 

public class ThreadTest extends Thread { public void run() { // 在這裡編寫執行緒執行的主體 // do something } }

這是實現Runnable介面實現多執行緒的示例: 

public class RunnableTest implements Runnable { public void run() { // 在這裡編寫執行緒執行的主體 // do something } }

這兩種實現方式的區別並不大。繼承Thread類的方式實現起來較為簡單,但是繼承它的類就不能再繼承別的類了,因此也就不能繼承別的類的有用的方法了。而使用是想Runnable介面的方式就不存在這個問題了,而且這種實現方式將執行緒主體和執行緒物件本身分離開來,邏輯上也較為清晰,所以推薦大家更多地採用這種方式。 

如何啟動執行緒 

我們通過以上兩種方式實現了一個執行緒之後,執行緒的例項並沒有被建立,因此它們也並沒有被執行。我們要啟動一個執行緒,必須呼叫方法來啟動它,這個方法就是Thread類的start()方法,而不是run()方法(既不是我們繼承Thread類重寫的run()方法,也不是實現Runnable介面的run()方法)。run()方法中包含的是執行緒的主體,也就是這個執行緒被啟動後將要執行的程式碼,它跟執行緒的啟動沒有任何關係。上面兩種實現執行緒的方式在啟動時會有所不同。 

繼承Thread類的啟動方式: 

public class ThreadStartTest { public static void main(String[] args) { // 建立一個執行緒例項 ThreadTest tt = new ThreadTest(); // 啟動執行緒 tt.start(); } }

實現Runnable介面的啟動方式: 

public class RunnableStartTest { public static void main(String[] args) { // 建立一個執行緒例項 Thread t = new Thread(new RunnableTest()); // 啟動執行緒 t.start(); } }

實際上這兩種啟動執行緒的方式原理是一樣的。首先都是呼叫本地方法啟動一個執行緒,其次是在這個執行緒裡執行目標物件的run()方法。那麼這個目標物件是什麼呢?為了弄明白這個問題,我們來看看Thread類的run()方法的實現: 

public void run() { if (target != null) { target.run(); } }

當我們採用實現Runnable介面的方式來實現執行緒的情況下,在呼叫new Thread(Runnable target)構造器時,將實現Runnable介面的類的例項設定成了執行緒要執行的主體所屬的目標物件target,當執行緒啟動時,這個例項的run()方法就被執行了。當我們採用繼承Thread的方式實現執行緒時,執行緒的這個run()方法被重寫了,所以當執行緒啟動時,執行的是這個物件自身的run()方法。總結起來就一句話,執行緒類有一個Runnable型別的target屬性,它是執行緒啟動後要執行的run()方法所屬的主體,如果我們採用的是繼承Thread類的方式,那麼這個target就是執行緒物件自身,如果我們採用的是實現Runnable介面的方式,那麼這個target就是實現了Runnable介面的類的例項。 

執行緒的狀態 

在Java 1.4及以下的版本中,每個執行緒都具有新建、可執行、阻塞、死亡四種狀態,但是在Java 5.0及以上版本中,執行緒的狀態被擴充為新建、可執行、阻塞、等待、定時等待、死亡六種。執行緒的狀態完全包含了一個執行緒從新建到執行,最後到結束的整個生命週期。執行緒狀態的具體資訊如下: 

  1. NEW(新建狀態、初始化狀態):執行緒物件已經被建立,但是還沒有被啟動時的狀態。這段時間就是在我們呼叫new命令之後,呼叫start()方法之前。
  2. RUNNABLE(可執行狀態、就緒狀態):在我們呼叫了執行緒的start()方法之後執行緒所處的狀態。處於RUNNABLE狀態的執行緒在JAVA虛擬機器(JVM)上是執行著的,但是它可能還正在等待作業系統分配給它相應的執行資源以得以執行。
  3. BLOCKED(阻塞狀態、被中斷執行):執行緒正在等待其它的執行緒釋放同步鎖,以進入一個同步塊或者同步方法繼續執行;或者它已經進入了某個同步塊或同步方法,在執行的過程中它呼叫了某個物件繼承自java.lang.Object的wait()方法,正在等待重新返回這個同步塊或同步方法。
  4. WAITING(等待狀態):當前執行緒呼叫了java.lang.Object.wait()、java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三個中的任意一個方法,正在等待另外一個執行緒執行某個操作。比如一個執行緒呼叫了某個物件的wait()方法,正在等待其它執行緒呼叫這個物件的notify()或者notifyAll()(這兩個方法同樣是繼承自Object類)方法來喚醒它;或者一個執行緒呼叫了另一個執行緒的join()(這個方法屬於Thread類)方法,正在等待這個方法執行結束。
  5. TIMED_WAITING(定時等待狀態):當前執行緒呼叫了java.lang.Object.wait(long timeout)、java.lang.Thread.join(long millis)、java.util.concurrent.locks.LockSupport.packNanos(long nanos)、java.util.concurrent.locks.LockSupport.packUntil(long deadline)四個方法中的任意一個,進入等待狀態,但是與WAITING狀態不同的是,它有一個最大等待時間,即使等待的條件仍然沒有滿足,只要到了這個時間它就會自動醒來。
  6. TERMINATED(死亡狀態、終止狀態):執行緒完成執行後的狀態。執行緒執行完run()方法中的全部程式碼,從該方法中退出,進入TERMINATED狀態。還有一種情況是run()在執行過程中丟擲了一個異常,而這個異常沒有被程式捕獲,導致這個執行緒異常終止進入TERMINATED狀態。

在Java5.0及以上版本中,執行緒的全部六種狀態都以列舉型別的形式定義在java.lang.Thread類中了,程式碼如下: 

public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }

sleep()和wait()的區別 

sleep()方法和wait()方法都成產生讓當前執行的執行緒停止執行的效果,這是它們的共同點。下面我們來詳細說說它們的不同之處。 

sleep()方法是本地方法,屬於Thread類,它有兩種定義: 

public static native void sleep(long millis) throws InterruptedException; public static void sleep(long millis, int nanos) throws InterruptedException { //other code }

其中的引數millis代表毫秒數(千分之一秒),nanos代表納秒數(十億分之一秒)。這兩個方法都可以讓呼叫它的執行緒沉睡(停止執行)指定的時間,到了這個時間,執行緒就會自動醒來,變為可執行狀態(RUNNABLE),但這並不表示它馬上就會被執行,因為執行緒排程機制恢復執行緒的執行也需要時間。呼叫sleep()方法並不會讓執行緒釋放它所持有的同步鎖;而且在這期間它也不會阻礙其它執行緒的執行。上面的連個方法都宣告丟擲一個InterruptedException型別的異常,這是因為執行緒在sleep()期間,有可能被持有它的引用的其它執行緒呼叫它的interrupt()方法而中斷。中斷一個執行緒會導致一個InterruptedException異常的產生,如果你的程式不捕獲這個異常,執行緒就會異常終止,進入TERMINATED狀態,如果你的程式捕獲了這個異常,那麼程式就會繼續執行catch語句塊(可能還有finally語句塊)以及以後的程式碼。 

為了更好地理解interrupt()效果,我們來看一下下面這個例子: 

public class InterruptTest { public static void main(String[] args) { Thread t = new Thread() { public void run() { try { System.out.println("我被執行了-在sleep()方法前"); // 停止執行10分鐘 Thread.sleep(1000 * 60 * 60 * 10); System.out.println("我被執行了-在sleep()方法後"); } catch (InterruptedException e) { System.out.println("我被執行了-在catch語句塊中"); } System.out.println("我被執行了-在try{}語句塊後"); } }; // 啟動執行緒 t.start(); // 在sleep()結束前中斷它 t.interrupt(); } }

執行結果: 

  1. 我被執行了-在sleep()方法前
  2. 我被執行了-在catch語句塊中
  3. 我被執行了-在try{}語句塊後

wait()方法也是本地方法,屬於Object類,有三個定義: 

public final void wait() throws InterruptedException { //do something } public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { //do something }

wari()和wait(long timeout,int nanos)方法都是基於wait(long timeout)方法實現的。同樣地,timeout代表毫秒數,nanos代表納秒數。當呼叫了某個物件的wait()方法時,當前執行的執行緒就會轉入等待狀態(WAITING),等待別的執行緒再次呼叫這個物件的notify()或者notifyAll()方法(這兩個方法也是本地方法)喚醒它,或者到了指定的最大等待時間,執行緒自動醒來。如果執行緒擁有某個或某些物件的同步鎖,那麼在呼叫了wait()後,這個執行緒就會釋放它持有的所有同步資源,而不限於這個被呼叫了wait()方法的物件。wait()方法同樣會被Thread類的interrupt()方法中斷,併產生一個InterruptedException異常,效果同sleep()方法被中斷一樣。 

實現同步的方式 

同步是多執行緒中的重要概念。同步的使用可以保證在多執行緒執行的環境中,程式不會產生設計之外的錯誤結果。同步的實現方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關鍵字。 

給一個方法增加synchronized修飾符之後就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類的抽象方法,也不能是介面中的介面方法。下面程式碼是一個同步方法的示例: 

public synchronized void aMethod() { // do something } public static synchronized void anotherMethod() { // do something }

執行緒在執行同步方法時是具有排它性的。當任意一個執行緒進入到一個物件的任意一個同步方法時,這個物件的所有同步方法都被鎖定了,在此期間,其他任何執行緒都不能訪問這個物件的任意一個同步方法,直到這個執行緒執行完它所呼叫的同步方法並從中退出,從而導致它釋放了該物件的同步鎖之後。在一個物件被某個執行緒鎖定之後,其他執行緒是可以訪問這個物件的所有非同步方法的。 

同步塊的形式雖然與同步方法不同,但是原理和效果是一致的。同步塊是通過鎖定一個指定的物件,來對同步塊中包含的程式碼進行同步;而同步方法是對這個方法塊裡的程式碼進行同步,而這種情況下鎖定的物件就是同步方法所屬的主體物件自身。如果這個方法是靜態同步方法呢?那麼執行緒鎖定的就不是這個類的物件了,也不是這個類自身,而是這個類對應的java.lang.Class型別的物件。同步方法和同步塊之間的相互制約只限於同一個物件之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的例項(物件)沒有關係。 

下面這段程式碼演示了同步塊的實現方式: 

public void test() { // 同步鎖 String lock = "LOCK"; // 同步塊 synchronized (lock) { // do something } int i = 0; // ... }

對於作為同步鎖的物件並沒有什麼特別要求,任意一個物件都可以。如果一個物件既有同步方法,又有同步塊,那麼當其中任意一個同步方法或者同步塊被某個執行緒執行時,這個物件就被鎖定了,其他執行緒無法在此時訪問這個物件的同步方法,也不能執行同步塊。 

synchronized和Lock 

Lock是一個介面,它位於Java 5.0新增的java.utils.concurrent包的子包locks中。concurrent包及其子包中的類都是用來處理多執行緒程式設計的。實現Lock介面的類具有與synchronized關鍵字同樣的功能,但是它更加強大一些。java.utils.concurrent.locks.ReentrantLock是較常用的實現了Lock介面的類。下面是ReentrantLock類的一個應用例項: 

private Lock lock = new ReentrantLock(); public void testLock() { // 鎖定物件 lock.lock(); try { // do something } finally { // 釋放對物件的鎖定 lock.unlock(); } }

lock()方法用於鎖定物件,unlock()方法用於釋放對物件的鎖定,他們都是在Lock介面中定義的方法。位於這兩個方法之間的程式碼在被執行時,效果等同於被放在synchronized同步塊中。一般用法是將需要在lock()和unlock()方法之間執行的程式碼放在try{}塊中,並且在finally{}塊中呼叫unlock()方法,這樣就可以保證即使在執行程式碼丟擲異常的情況下,物件的鎖也總是會被釋放,否則的話就會為死鎖的產生增加可能。 

使用synchronized關鍵字實現的同步,會把一個物件的所有同步方法和同步塊看做一個整體,只要有一個被某個執行緒呼叫了,其他的就無法被別的執行緒執行,即使這些方法或同步塊與被呼叫的程式碼之間沒有任何邏輯關係,這顯然降低了程式的執行效率。而使用Lock就能夠很好地解決這個問題。我們可以把一個物件中按照邏輯關係把需要同步的方法或程式碼進行分組,為每個組建立一個Lock型別的物件,對實現同步。那麼,當一個同步塊被執行時,這個執行緒只會鎖定與當前執行程式碼相關的其他程式碼最小集合,而並不影響其他執行緒對其餘同步程式碼的呼叫執行。 

關於死鎖 

死鎖就是一個程序中的每個執行緒都在等待這個程序中的其他執行緒釋放所佔用的資源,從而導致所有執行緒都無法繼續執行的情況。死鎖是多執行緒程式設計中一個隱藏的陷阱,它經常發生在多個執行緒共用資源的時候。在實際開發中,死鎖一般隱藏的較深,不容易被發現,一旦死鎖現象發生,就必然會導致程式的癱瘓。因此必須避免它的發生。 

程式中必須同時滿足以下四個條件才會引發死鎖: 

  1. 互斥(Mutual exclusion):執行緒所使用的資源中至少有一個是不能共享的,它在同一時刻只能由一個執行緒使用。
  2. 持有與等待(Hold and wait):至少有一個執行緒已經持有了資源,並且正在等待獲取其他的執行緒所持有的資源。
  3. 非搶佔式(No pre-emption):如果一個執行緒已經持有了某個資源,那麼在這個執行緒釋放這個資源之前,別的執行緒不能把它搶奪過去使用。
  4. 迴圈等待(Circular wait):假設有N個執行緒在執行,第一個執行緒持有了一個資源,並且正在等待獲取第二個執行緒持有的資源,而第二個執行緒正在等待獲取第三個執行緒持有的資源,依此類推……第N個執行緒正在等待獲取第一個執行緒持有的資源,由此形成一個迴圈等待。

執行緒池 

執行緒池就像資料庫連線池一樣,是一個物件池。所有的物件池都有一個共同的目的,那就是為了提高物件的使用率,從而達到提高程式效率的目的。比如對於Servlet,它被設計為多執行緒的(如果它是單執行緒的,你就可以想象,當1000個人同時請求一個網頁時,在第一個人獲得請求結果之前,其它999個人都在鬱悶地等待),如果為每個使用者的每一次請求都建立一個新的執行緒物件來執行的話,系統就會在建立執行緒和銷燬執行緒上耗費很大的開銷,大大降低系統的效率。因此,Servlet多執行緒機制背後有一個執行緒池在支援,執行緒池在初始化初期就建立了一定數量的執行緒物件,通過提高對這些物件的利用率,避免高頻率地建立物件,從而達到提高程式的效率的目的。 

下面實現一個最簡單的執行緒池,從中理解它的實現原理。為此我們定義了四個類,它們的用途及具體實現如下: 

  1. Task(任務):這是個代表任務的抽象類,其中定義了一個deal()方法,繼承Task抽象類的子類需要實現這個方法,並把這個任務需要完成的具體工作在deal()方法編碼實現。執行緒池中的執行緒之所以被建立,就是為了執行各種各樣數量繁多的任務的,為了方便執行緒對任務的處理,我們需要用Task抽象類來保證任務的具體工作統一放在deal()方法裡來完成,這樣也使程式碼更加規範。 
    Task的定義如下: 

    public abstract class Task { public enum State { /* 新建 */NEW, /* 執行中 */RUNNING, /* 已完成 */FINISHED } // 任務狀態 private State state = State.NEW; public void setState(State state) { this.state = state; } public State getState() { return state; } public abstract void deal(); }
  2. TaskQueue(任務佇列):在同一時刻,可能有很多工需要執行,而程式在同一時刻只能執行一定數量的任務,當需要執行的任務數超過了程式所能承受的任務數時怎麼辦呢?這就有了先執行哪些任務,後執行哪些任務的規則。TaskQueue類就定義了這些規則中的一種,它採用的是FIFO(先進先出,英文名是First In First Out)的方式,也就是按照任務到達的先後順序執行。 
    TaskQueue類的定義如下: 

    import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class TaskQueue { private List<Task> queue = new LinkedList<Task>(); // 新增一項任務 public synchronized void addTask(Task task) { if (task != null) { queue.add(task); } } // 完成任務後將它從任務佇列中刪除 public synchronized void finishTask(Task task) { if (task != null) { task.setState(Task.State.FINISHED); queue.remove(task); } } // 取得一項待執行任務 public synchronized Task getTask() { Iterator<Task> it = queue.iterator(); Task task; while (it.hasNext()) { task = it.next(); // 尋找一個新建的任務 if (Task.State.NEW.equals(task.getState())) { // 把任務狀態置為執行中 task.setState(Task.State.RUNNING); return task; } } return null; } }

    addTask(Task task)方法用於當一個新的任務到達時,將它新增到任務佇列中。這裡使用了LinkedList類來儲存任務到達的先後順序。finishTask(Task task)方法用於任務被執行完畢時,將它從任務佇列中清除出去。getTask()方法用於取得當前要執行的任務。

  3. TaskThread(執行任務的執行緒):它繼承自Thread類,專門用於執行任務佇列中的待執行任務。
    public class TaskThread extends Thread { // 該執行緒所屬的執行緒池 private ThreadPoolService service; public TaskThread(ThreadPoolService tps) { service = tps; } public void run() { // 線上程池執行的狀態下執行任務佇列中的任務 while (service.isRunning()) { TaskQueue queue = service.getTaskQueue(); Task task = queue.getTask(); if (task != null) { task.deal(); } queue.finishTask(task); } } }
  4. ThreadPoolService(執行緒池服務類):這是執行緒池最核心的一個類。它在被建立了時候就建立了幾個執行緒物件,但是這些執行緒並沒有啟動執行,但呼叫了start()方法啟動執行緒池服務時,它們才真正執行。stop()方法可以停止執行緒池服務,同時停止池中所有執行緒的執行。而runTask(Task task)方法是將一個新的待執行任務交與執行緒池來執行。 
    ThreadPoolService類的定義如下: 

    import java.util.ArrayList; import java.util.List; public class ThreadPoolService { // 執行緒數 public static final int THREAD_COUNT = 5; // 執行緒池狀態 private Status status = Status.NEW; private TaskQueue queue = new TaskQueue(); public enum Status { /* 新建 */NEW, /* 提供服務中 */RUNNING, /* 停止服務 */TERMINATED, } private List<Thread> threads = new ArrayList<Thread>(); public ThreadPoolService() { for (int i = 0; i < THREAD_COUNT; i  ) { Thread t = new TaskThread(this); threads.add(t); } } // 啟動服務 public void start() { this.status = Status.RUNNING; for (int i = 0; i < THREAD_COUNT; i  ) { threads.get(i).start(); } } // 停止服務 public void stop() { this.status = Status.TERMINATED; } // 是否正在執行 public boolean isRunning() { return status == Status.RUNNING; } // 執行任務 public void runTask(Task task) { queue.addTask(task); } protected TaskQueue getTaskQueue() { return queue; } }

完成了上面四個類,我們就實現了一個簡單的執行緒池。現在我們就可以使用它了,下面的程式碼做了一個簡單的示例:

public class SimpleTaskTest extends Task { @Override public void deal() { // do something } public static void main(String[] args) throws InterruptedException { ThreadPoolService service = new ThreadPoolService(); service.start(); // 執行十次任務 for (int i = 0; i < 10; i  ) { service.runTask(new SimpleTaskTest()); } // 睡眠1秒鐘,等待所有任務執行完畢 Thread.sleep(1000); service.stop(); } }

當然,我們實現的是最簡單的,這裡只是為了演示執行緒池的實現原理。在實際應用中,根據情況的不同,可以做很多優化。比如: 

  • 調整任務佇列的規則,給任務設定優先順序,級別高的任務優先執行。
  • 動態維護執行緒池,當待執行任務數量較多時,增加執行緒的數量,加快任務的執行速度;當任務較少時,回收一部分長期閒置的執行緒,減少對系統資源的消耗。

事實上Java5.0及以上版本已經為我們提供了執行緒池功能,無需再重新實現。這些類位於java.util.concurrent包中。 

Executors類提供了一組建立執行緒池物件的方法,常用的有一下幾個: 

public static ExecutorService newCachedThreadPool() { // other code } public static ExecutorService newFixedThreadPool(int nThreads) { // other code } public static ExecutorService newSingleThreadExecutor() { // other code }

newCachedThreadPool()方法建立一個動態的執行緒池,其中執行緒的數量會根據實際需要來建立和回收,適合於執行大量短期任務的情況;newFixedThreadPool(int nThreads)方法建立一個包含固定數量執行緒物件的執行緒池,nThreads代表要建立的執行緒數,如果某個執行緒在執行的過程中因為異常而終止了,那麼一個新的執行緒會被建立和啟動來代替它;而newSingleThreadExecutor()方法則只線上程池中建立一個執行緒,來執行所有的任務。 

這三個方法都返回了一個ExecutorService型別的物件。實際上,ExecutorService是一個介面,它的submit()方法負責接收任務並交與執行緒池中的執行緒去執行。submit()方法能夠接受Callable和Runnable兩種型別的物件。它們的用法和區別如下: 

  1. Runnable介面:繼承Runnable介面的類要實現它的run()方法,並將執行任務的程式碼放入其中,run()方法沒有返回值。適合於只做某種操作,不關心執行結果的情況。
  2. Callable介面:繼承Callable介面的類要實現它的call()方法,並將執行任務的程式碼放入其中,call()將任務的執行結果作為返回值。適合於執行某種操作後,需要知道執行結果的情況。

無論是接收Runnable型引數,還是接收Callable型引數的submit()方法,都會返回一個Future(也是一個介面)型別的物件。該物件中包含了任務的執行情況以及結果。呼叫Future的boolean isDone()方法可以獲知任務是否執行完畢;呼叫Object get()方法可以獲得任務執行後的返回結果,如果此時任務還沒有執行完,get()方法會保持等待,直到相應的任務執行完畢後,才會將結果返回。 

我們用下面的一個例子來演示Java5.0中執行緒池的使用: 

import java.util.concurrent.*; public class ExecutorTest { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService es = Executors.newSingleThreadExecutor(); Future fr = es.submit(new RunnableTest());// 提交任務 Future fc = es.submit(new CallableTest());// 提交任務 // 取得返回值並輸出 System.out.println((String) fc.get()); // 檢查任務是否執行完畢 if (fr.isDone()) { System.out.println("執行完畢-RunnableTest.run()"); } else { System.out.println("未執行完-RunnableTest.run()"); } // 檢查任務是否執行完畢 if (fc.isDone()) { System.out.println("執行完畢-CallableTest.run()"); } else { System.out.println("未執行完-CallableTest.run()"); } // 停止執行緒池服務 es.shutdown(); } } class RunnableTest implements Runnable { public void run() { System.out.println("已經執行-RunnableTest.run()"); } } class CallableTest implements Callable { public Object call() { System.out.println("已經執行-CallableTest.call()"); return "返回值-CallableTest.call()"; } }

執行結果: 

  1. 已經執行-RunnableTest.run()
  2. 已經執行-CallableTest.call()
  3. 返回值-CallableTest.call()
  4. 執行完畢-RunnableTest.run()
  5. 執行完畢-CallableTest.run()

使用完執行緒池之後,需要呼叫它的shutdown()方法停止服務,否則其中的所有執行緒都會保持執行,程式不會退出。