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等),你當然不希望它們在收取新郵件的時候,導致你連已經收下來的郵件都無法閱讀,而只能等待收取郵件操作執行完畢。這正是線程的意義所在。
實現線程的方式
實現線程的方式有兩種:
繼承java.lang.Thread,並重寫它的run()方法,將線程的執行主體放入其中。實現java.lang.Runnable接口,實現它的run()方法,並將線程的執行主體放入其中。
這是繼承Thread類實現線程的示例:
[java]view plaincopy
- publicclassThreadTestextendsThread{
- publicvoidrun(){
- //在這裡編寫線程執行的主體
- //dosomething
- }
- }
這是實現Runnable接口實現多線程的示例:
[java]view plaincopy
- publicclassRunnableTestimplementsRunnable{
- publicvoidrun(){
- //在這裡編寫線程執行的主體
- //dosomething
- }
- }
這兩種實現方式的區別並不大。繼承Thread類的方式實現起來較為簡單,但是繼承它的類就不能再繼承別的類了,因此也就不能繼承別的類的有用的方法了。而使用是想Runnable接口的方式就不存在這個問題了,而且這種實現方式將線程主體和線程對象本身分離開來,邏輯上也較為清晰,所以推薦大家更多地采用這種方式。
如何啟動線程
我們通過以上兩種方式實現了一個線程之後,線程的實例並沒有被創建,因此它們也並沒有被運行。我們要啟動一個線程,必須調用方法來啟動它,這個方法就是Thread類的start()方法,而不是run()方法(既不是我們繼承Thread類重寫的run()方法,也不是實現Runnable接口的run()方法)。run()方法中包含的是線程的主體,也就是這個線程被啟動後將要運行的代碼,它跟線程的啟動沒有任何關系。上面兩種實現線程的方式在啟動時會有所不同。
繼承Thread類的啟動方式:
[java]view plaincopy
- publicclassThreadStartTest{
- publicstaticvoidmain(String[]args){
- //創建一個線程實例
- ThreadTesttt=newThreadTest();
- //啟動線程
- tt.start();
- }
- }
實現Runnable接口的啟動方式:
[java]view plaincopy
- publicclassRunnableStartTest{
- publicstaticvoidmain(String[]args){
- //創建一個線程實例
- Threadt=newThread(newRunnableTest());
- //啟動線程
- t.start();
- }
- }
實際上這兩種啟動線程的方式原理是一樣的。首先都是調用本地方法啟動一個線程,其次是在這個線程裡執行目標對象的run()方法。那麼這個目標對象是什麼呢?為了弄明白這個問題,我們來看看Thread類的run()方法的實現:
[java]view plaincopy
- publicvoidrun(){
- 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及以上版本中,線程的狀態被擴充為新建、可運行、阻塞、等待、定時等待、死亡六種。線程的狀態完全包含了一個線程從新建到運行,最後到結束的整個生命周期。線程狀態的具體信息如下:
NEW(新建狀態、初始化狀態):線程對象已經被創建,但是還沒有被啟動時的狀態。這段時間就是在我們調用new命令之後,調用start()方法之前。
RUNNABLE(可運行狀態、就緒狀態):在我們調用了線程的start()方法之後線程所處的狀態。處於RUNNABLE狀態的線程在JAVA虛擬機(JVM)上是運行著的,但是它可能還正在等待操作系統分配給它相應的運行資源以得以運行。
BLOCKED(阻塞狀態、被中斷運行):線程正在等待其它的線程釋放同步鎖,以進入一個同步塊或者同步方法繼續運行;或者它已經進入了某個同步塊或同步方法,在運行的過程中它調用了某個對象繼承自java.lang.Object的wait()方法,正在等待重新返回這個同步塊或同步方法。
WAITING(等待狀態):當前線程調用了java.lang.Object.wait()、java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三個中的任意一個方法,正在等待另外一個線程執行某個操作。比如一個線程調用了某個對象的wait()方法,正在等待其它線程調用這個對象的notify()或者notifyAll()(這兩個方法同樣是繼承自Object類)方法來喚醒它;或者一個線程調用了另一個線程的join()(這個方法屬於Thread類)方法,正在等待這個方法運行結束。
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狀態不同的是,它有一個最大等待時間,即使等待的條件仍然沒有滿足,只要到了這個時間它就會自動醒來。
TERMINATED(死亡狀態、終止狀態):線程完成執行後的狀態。線程執行完run()方法中的全部代碼,從該方法中退出,進入TERMINATED狀態。還有一種情況是run()在運行過程中拋出了一個異常,而這個異常沒有被程序捕獲,導致這個線程異常終止進入TERMINATED狀態。
在Java5.0及以上版本中,線程的全部六種狀態都以枚舉類型的形式定義在java.lang.Thread類中了,代碼如下:
[java]view plaincopy
- publicenumState{
- NEW,
- RUNNABLE,
- BLOCKED,
- WAITING,
- TIMED_WAITING,
- TERMINATED;
- }
sleep()和wait()的區別
sleep()方法和wait()方法都成產生讓當前運行的線程停止運行的效果,這是它們的共同點。下面我們來詳細說說它們的不同之處。
sleep()方法是本地方法,屬於Thread類,它有兩種定義:
[java]view plaincopy
- publicstaticnativevoidsleep(longmillis)throwsInterruptedException;
- publicstaticvoidsleep(longmillis,intnanos)throwsInterruptedException{
- //othercode
- }
其中的參數millis代表毫秒數(千分之一秒),nanos代表納秒數(十億分之一秒)。這兩個方法都可以讓調用它的線程沉睡(停止運行)指定的時間,到了這個時間,線程就會自動醒來,變為可運行狀態(RUNNABLE),但這並不表示它馬上就會被運行,因為線程調度機制恢復線程的運行也需要時間。調用sleep()方法並不會讓線程釋放它所持有的同步鎖;而且在這期間它也不會阻礙其它線程的運行。上面的連個方法都聲明拋出一個InterruptedException類型的異常,這是因為線程在sleep()期間,有可能被持有它的引用的其它線程調用它的interrupt()方法而中斷。中斷一個線程會導致一個InterruptedException異常的產生,如果你的程序不捕獲這個異常,線程就會異常終止,進入TERMINATED狀態,如果你的程序捕獲了這個異常,那麼程序就會繼續執行catch語句塊(可能還有finally語句塊)以及以後的代碼。
為了更好地理解interrupt()效果,我們來看一下下面這個例子:
[java]view plaincopy
- publicclassInterruptTest{
- publicstaticvoidmain(String[]args){
- Threadt=newThread(){
- publicvoidrun(){
- try{
- System.out.println("我被執行了-在sleep()方法前");
- //停止運行10分鐘
- Thread.sleep(1000*60*60*10);
- System.out.println("我被執行了-在sleep()方法後");
- }catch(InterruptedExceptione){
- System.out.println("我被執行了-在catch語句塊中");
- }
- System.out.println("我被執行了-在try{}語句塊後");
- }
- };
- //啟動線程
- t.start();
- //在sleep()結束前中斷它
- t.interrupt();
- }
- }
運行結果:
我被執行了-在sleep()方法前我被執行了-在catch語句塊中我被執行了-在try{}語句塊後
wait()方法也是本地方法,屬於Object類,有三個定義:
[java]view plaincopy
- publicfinalvoidwait()throwsInterruptedException{
- //dosomething
- }
- publicfinalnativevoidwait(longtimeout)throwsInterruptedException;
- publicfinalvoidwait(longtimeout,intnanos)throwsInterruptedException{
- //dosomething
- }
wari()和wait(long timeout,int nanos)方法都是基於wait(long timeout)方法實現的。同樣地,timeout代表毫秒數,nanos代表納秒數。當調用了某個對象的wait()方法時,當前運行的線程就會轉入等待狀態(WAITING),等待別的線程再次調用這個對象的notify()或者notifyAll()方法(這兩個方法也是本地方法)喚醒它,或者到了指定的最大等待時間,線程自動醒來。如果線程擁有某個或某些對象的同步鎖,那麼在調用了wait()後,這個線程就會釋放它持有的所有同步資源,而不限於這個被調用了wait()方法的對象。wait()方法同樣會被Thread類的interrupt()方法中斷,並產生一個InterruptedException異常,效果同sleep()方法被中斷一樣。
實現同步的方式
同步是多線程中的重要概念。同步的使用可以保證在多線程運行的環境中,程序不會產生設計之外的錯誤結果。同步的實現方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關鍵字。
給一個方法增加synchronized修飾符之後就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類的抽象方法,也不能是接口中的接口方法。下面代碼是一個同步方法的示例:
[java]view plaincopy
- publicsynchronizedvoidaMethod(){
- //dosomething
- }
- publicstaticsynchronizedvoidanotherMethod(){
- //dosomething
- }
線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法並從中退出,從而導致它釋放了該對象的同步鎖之後。在一個對象被某個線程鎖定之後,其他線程是可以訪問這個對象的所有非同步方法的。
同步塊的形式雖然與同步方法不同,但是原理和效果是一致的。同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊裡的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。如果這個方法是靜態同步方法呢?那麼線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限於同一個對象之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的實例(對象)沒有關系。
下面這段代碼演示了同步塊的實現方式:
[java]view plaincopy
- publicvoidtest(){
- //同步鎖
- Stringlock="LOCK";
- //同步塊
- synchronized(lock){
- //dosomething
- }
- inti=0;
- //...
- }
對於作為同步鎖的對象並沒有什麼特別要求,任意一個對象都可以。如果一個對象既有同步方法,又有同步塊,那麼當其中任意一個同步方法或者同步塊被某個線程執行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執行同步塊。
synchronized和Lock
Lock是一個接口,它位於Java 5.0新增的java.utils.concurrent包的子包locks中。concurrent包及其子包中的類都是用來處理多線程編程的。實現Lock接口的類具有與synchronized關鍵字同樣的功能,但是它更加強大一些。java.utils.concurrent.locks.ReentrantLock是較常用的實現了Lock接口的類。下面是ReentrantLock類的一個應用實例:
[java]view plaincopy
- privateLocklock=newReentrantLock();
- publicvoidtestLock(){
- //鎖定對象
- lock.lock();
- try{
- //dosomething
- }finally{
- //釋放對對象的鎖定
- lock.unlock();
- }
- }
lock()方法用於鎖定對象,unlock()方法用於釋放對對象的鎖定,他們都是在Lock接口中定義的方法。位於這兩個方法之間的代碼在被執行時,效果等同於被放在synchronized同步塊中。一般用法是將需要在lock()和unlock()方法之間執行的代碼放在try{}塊中,並且在finally{}塊中調用unlock()方法,這樣就可以保證即使在執行代碼拋出異常的情況下,對象的鎖也總是會被釋放,否則的話就會為死鎖的產生增加可能。
使用synchronized關鍵字實現的同步,會把一個對象的所有同步方法和同步塊看做一個整體,只要有一個被某個線程調用了,其他的就無法被別的線程執行,即使這些方法或同步塊與被調用的代碼之間沒有任何邏輯關系,這顯然降低了程序的運行效率。而使用Lock就能夠很好地解決這個問題。我們可以把一個對象中按照邏輯關系把需要同步的方法或代碼進行分組,為每個組創建一個Lock類型的對象,對實現同步。那麼,當一個同步塊被執行時,這個線程只會鎖定與當前運行代碼相關的其他代碼最小集合,而並不影響其他線程對其余同步代碼的調用執行。
關於死鎖
死鎖就是一個進程中的每個線程都在等待這個進程中的其他線程釋放所占用的資源,從而導致所有線程都無法繼續執行的情況。死鎖是多線程編程中一個隱藏的陷阱,它經常發生在多個線程共用資源的時候。在實際開發中,死鎖一般隱藏的較深,不容易被發現,一旦死鎖現象發生,就必然會導致程序的癱瘓。因此必須避免它的發生。
程序中必須同時滿足以下四個條件才會引發死鎖:
互斥(Mutual exclusion):線程所使用的資源中至少有一個是不能共享的,它在同一時刻只能由一個線程使用。
持有與等待(Hold and wait):至少有一個線程已經持有了資源,並且正在等待獲取其他的線程所持有的資源。
非搶占式(No pre-emption):如果一個線程已經持有了某個資源,那麼在這個線程釋放這個資源之前,別的線程不能把它搶奪過去使用。
循環等待(Circular wait):假設有N個線程在運行,第一個線程持有了一個資源,並且正在等待獲取第二個線程持有的資源,而第二個線程正在等待獲取第三個線程持有的資源,依此類推……第N個線程正在等待獲取第一個線程持有的資源,由此形成一個循環等待。
線程池
線程池就像數據庫連接池一樣,是一個對象池。所有的對象池都有一個共同的目的,那就是為了提高對象的使用率,從而達到提高程序效率的目的。比如對於Servlet,它被設計為多線程的(如果它是單線程的,你就可以想象,當1000個人同時請求一個網頁時,在第一個人獲得請求結果之前,其它999個人都在郁悶地等待),如果為每個用戶的每一次請求都創建一個新的線程對象來運行的話,系統就會在創建線程和銷毀線程上耗費很大的開銷,大大降低系統的效率。因此,Servlet多線程機制背後有一個線程池在支持,線程池在初始化初期就創建了一定數量的線程對象,通過提高對這些對象的利用率,避免高頻率地創建對象,從而達到提高程序的效率的目的。
下面實現一個最簡單的線程池,從中理解它的實現原理。為此我們定義了四個類,它們的用途及具體實現如下:
Task(任務):這是個代表任務的抽象類,其中定義了一個deal()方法,繼承Task抽象類的子類需要實現這個方法,並把這個任務需要完成的具體工作在deal()方法編碼實現。線程池中的線程之所以被創建,就是為了執行各種各樣數量繁多的任務的,為了方便線程對任務的處理,我們需要用Task抽象類來保證任務的具體工作統一放在deal()方法裡來完成,這樣也使代碼更加規范。
Task的定義如下:
[java]view plaincopy
- publicabstractclassTask{
- publicenumState{
- /*新建*/NEW,/*執行中*/RUNNING,/*已完成*/FINISHED
- }
- //任務狀態
- privateStatestate=State.NEW;
- publicvoidsetState(Statestate){
- this.state=state;
- }
- publicStategetState(){
- returnstate;
- }
- publicabstractvoiddeal();
- }
TaskQueue(任務隊列):在同一時刻,可能有很多任務需要執行,而程序在同一時刻只能執行一定數量的任務,當需要執行的任務數超過了程序所能承受的任務數時怎麼辦呢?這就有了先執行哪些任務,後執行哪些任務的規則。TaskQueue類就定義了這些規則中的一種,它采用的是FIFO(先進先出,英文名是First In First Out)的方式,也就是按照任務到達的先後順序執行。
TaskQueue類的定義如下:
[java]view plaincopy
- importjava.util.Iterator;
- importjava.util.LinkedList;
- importjava.util.List;
- publicclassTaskQueue{
- privateListqueue=newLinkedList();
- //添加一項任務
- publicsynchronizedvoidaddTask(Tasktask){
- if(task!=null){
- queue.add(task);
- }
- }
- //完成任務後將它從任務隊列中刪除
- publicsynchronizedvoidfinishTask(Tasktask){
- if(task!=null){
- task.setState(Task.State.FINISHED);
- queue.remove(task);
- }
- }
- //取得一項待執行任務
- publicsynchronizedTaskgetTask(){
- Iteratorit=queue.iterator();
- Tasktask;
- while(it.hasNext()){
- task=it.next();
- //尋找一個新建的任務
- if(Task.State.NEW.equals(task.getState())){
- //把任務狀態置為運行中
- task.setState(Task.State.RUNNING);
- returntask;
- }
- }
- returnnull;
- }
- }
addTask(Task task)方法用於當一個新的任務到達時,將它添加到任務隊列中。這裡使用了LinkedList類來保存任務到達的先後順序。finishTask(Task task)方法用於任務被執行完畢時,將它從任務隊列中清除出去。getTask()方法用於取得當前要執行的任務。
TaskThread(執行任務的線程):它繼承自Thread類,專門用於執行任務隊列中的待執行任務。
[java]view plaincopy
- publicclassTaskThreadextendsThread{
- //該線程所屬的線程池
- privateThreadPoolServiceservice;
- publicTaskThread(ThreadPoolServicetps){
- service=tps;
- }
- publicvoidrun(){
- //在線程池運行的狀態下執行任務隊列中的任務
- while(service.isRunning()){
- TaskQueuequeue=service.getTaskQueue();
- Tasktask=queue.getTask();
- if(task!=null){
- task.deal();
- }
- queue.finishTask(task);
- }
- }
- }
ThreadPoolService(線程池服務類):這是線程池最核心的一個類。它在被創建了時候就創建了幾個線程對象,但是這些線程並沒有啟動運行,但調用了start()方法啟動線程池服務時,它們才真正運行。stop()方法可以停止線程池服務,同時停止池中所有線程的運行。而runTask(Task task)方法是將一個新的待執行任務交與線程池來運行。
ThreadPoolService類的定義如下:
[java]view plaincopy
- importjava.util.ArrayList;
- importjava.util.List;
- publicclassThreadPoolService{
- //線程數
- publicstaticfinalintTHREAD_COUNT=5;
- //線程池狀態
- privateStatusstatus=Status.NEW;
- privateTaskQueuequeue=newTaskQueue();
- publicenumStatus{
- /*新建*/NEW,/*提供服務中*/RUNNING,/*停止服務*/TERMINATED,
- }
- privateListthreads=newArrayList();
- publicThreadPoolService(){
- for(inti=0;i Threadt=newTaskThread(this);
- threads.add(t);
- }
- }
- //啟動服務
- publicvoidstart(){
- this.status=Status.RUNNING;
- for(inti=0;i threads.get(i).start();
- }
- }
- //停止服務
- publicvoidstop(){
- this.status=Status.TERMINATED;
- }
- //是否正在運行
- publicbooleanisRunning(){
- returnstatus==Status.RUNNING;
- }
- //執行任務
- publicvoidrunTask(Tasktask){
- queue.addTask(task);
- }
- protectedTaskQueuegetTaskQueue(){
- returnqueue;
- }
- }
完成了上面四個類,我們就實現了一個簡單的線程池。現在我們就可以使用它了,下面的代碼做了一個簡單的示例:
[java]view plaincopy
- publicclassSimpleTaskTestextendsTask{
- @Override
- publicvoiddeal(){
- //dosomething
- }
- publicstaticvoidmain(String[]args)throwsInterruptedException{
- ThreadPoolServiceservice=newThreadPoolService();
- service.start();
- //執行十次任務
- for(inti=0;i<10;i++){
- service.runTask(newSimpleTaskTest());
- }
- //睡眠1秒鐘,等待所有任務執行完畢
- Thread.sleep(1000);
- service.stop();
- }
- }
當然,我們實現的是最簡單的,這裡只是為了演示線程池的實現原理。在實際應用中,根據情況的不同,可以做很多優化。比如:
調整任務隊列的規則,給任務設置優先級,級別高的任務優先執行。動態維護線程池,當待執行任務數量較多時,增加線程的數量,加快任務的執行速度;當任務較少時,回收一部分長期閒置的線程,減少對系統資源的消耗。
事實上Java5.0及以上版本已經為我們提供了線程池功能,無需再重新實現。這些類位於java.util.concurrent包中。
Executors類提供了一組創建線程池對象的方法,常用的有一下幾個:
[java]view plaincopy
- publicstaticExecutorServicenewCachedThreadPool(){
- //othercode
- }
- publicstaticExecutorServicenewFixedThreadPool(intnThreads){
- //othercode
- }
- publicstaticExecutorServicenewSingleThreadExecutor(){
- //othercode
- }
newCachedThreadPool()方法創建一個動態的線程池,其中線程的數量會根據實際需要來創建和回收,適合於執行大量短期任務的情況;newFixedThreadPool(int nThreads)方法創建一個包含固定數量線程對象的線程池,nThreads代表要創建的線程數,如果某個線程在運行的過程中因為異常而終止了,那麼一個新的線程會被創建和啟動來代替它;而newSingleThreadExecutor()方法則只在線程池中創建一個線程,來執行所有的任務。
這三個方法都返回了一個ExecutorService類型的對象。實際上,ExecutorService是一個接口,它的submit()方法負責接收任務並交與線程池中的線程去運行。submit()方法能夠接受Callable和Runnable兩種類型的對象。它們的用法和區別如下:
Runnable接口:繼承Runnable接口的類要實現它的run()方法,並將執行任務的代碼放入其中,run()方法沒有返回值。適合於只做某種操作,不關心運行結果的情況。
Callable接口:繼承Callable接口的類要實現它的call()方法,並將執行任務的代碼放入其中,call()將任務的執行結果作為返回值。適合於執行某種操作後,需要知道執行結果的情況。
無論是接收Runnable型參數,還是接收Callable型參數的submit()方法,都會返回一個Future(也是一個接口)類型的對象。該對象中包含了任務的執行情況以及結果。調用Future的boolean isDone()方法可以獲知任務是否執行完畢;調用Object get()方法可以獲得任務執行後的返回結果,如果此時任務還沒有執行完,get()方法會保持等待,直到相應的任務執行完畢後,才會將結果返回。
我們用下面的一個例子來演示Java5.0中線程池的使用:
[java]view plaincopy
- importjava.util.concurrent.*;
- publicclassExecutorTest{
- publicstaticvoidmain(String[]args)throwsInterruptedException,
- ExecutionException{
- ExecutorServicees=Executors.newSingleThreadExecutor();
- Futurefr=es.submit(newRunnableTest());//提交任務
- Futurefc=es.submit(newCallableTest());//提交任務
- //取得返回值並輸出
- 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();
- }
- }
- classRunnableTestimplementsRunnable{
- publicvoidrun(){
- System.out.println("已經執行-RunnableTest.run()");
- }
- }
- classCallableTestimplementsCallable{
- publicObjectcall(){
- System.out.println("已經執行-CallableTest.call()");
- return"返回值-CallableTest.call()";
- }
- }
運行結果:
- 已經執行-RunnableTest.run()已經執行-CallableTest.call()返回值-CallableTest.call()執行完畢-RunnableTest.run()執行完畢-CallableTest.run()
使用完線程池之後,需要調用它的shutdown()方法停止服務,否則其中的所有線程都會保持運行,程序不會退出。