線程中sleep和wait有什么區(qū)別 2012-03-16 14:24 229人閱讀 評(píng)論(0) 收藏 舉報(bào) 1、這兩個(gè)方法來(lái)自不同的類(lèi)分別是Thread和Object 2、最主要是sleep方法沒(méi)有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。 3、wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在 任何地方使用(使用范圍) synchronized(x){ x.notify() //或者wait() } 4、sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常 擴(kuò)充閱讀: java 線程中的sleep和wait有一個(gè)共同作用,停止當(dāng)前線程任務(wù)運(yùn)行,但他們存在一定的不同,首先我們先看sleep中的構(gòu)造函數(shù) 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類(lèi)中方法,表示讓一個(gè)線程進(jìn)入睡眠狀態(tài),等待一定的時(shí)間之后,自動(dòng)醒來(lái)進(jìn)入到可運(yùn)行狀態(tài),不會(huì)馬上進(jìn)入運(yùn)行狀態(tài),因?yàn)榫€程調(diào)度機(jī)制恢復(fù)線程的運(yùn)行也需要時(shí)間,一個(gè)線程對(duì)象調(diào)用了sleep方法之后,并不會(huì)釋放他所持有的所有對(duì)象鎖,所以也就不會(huì)影響其他進(jìn)程對(duì)象的運(yùn)行。但在sleep的過(guò)程中過(guò)程中有可能被其他對(duì)象調(diào)用它的interrupt(),產(chǎn)生InterruptedException異常,如果你的程序不捕獲這個(gè)異常,線程就會(huì)異常終止,進(jìn)入TERMINATED狀態(tài),如果你的程序捕獲了這個(gè)異常,那么程序就會(huì)繼續(xù)執(zhí)行catch語(yǔ)句塊(可能還有finally語(yǔ)句塊)以及以后的代碼。 注意sleep()方法是一個(gè)靜態(tài)方法,也就是說(shuō)他只對(duì)當(dāng)前對(duì)象有效,通過(guò)t.sleep()讓t對(duì)象進(jìn)入sleep,這樣的做法是錯(cuò)誤的,它只會(huì)是使當(dāng)前線程被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的成員方法,一旦一個(gè)對(duì)象調(diào)用了wait方法,必須要采用notify()和notifyAll()方法喚醒該進(jìn)程;如果線程擁有某個(gè)或某些對(duì)象的同步鎖,那么在調(diào)用了wait()后,這個(gè)線程就會(huì)釋放它持有的所有同步資源,而不限于這個(gè)被調(diào)用了wait()方法的對(duì)象。wait()方法也同樣會(huì)在wait的過(guò)程中有可能被其他對(duì)象調(diào)用interrupt()方法而產(chǎn)生 InterruptedException,效果以及處理方式同sleep()方法 追加內(nèi)容: Collection是個(gè)java.util下的接口,它是各種集合結(jié)構(gòu)的父接口。 Collections是個(gè)java.util下的類(lèi),它包含有各種有關(guān)集合操作的靜態(tài)方法。 Collection 層次結(jié)構(gòu)中的根接口。Collection 表示一組對(duì)象,這些對(duì)象也稱(chēng)為 collection的元素。一些 collection 允許有重復(fù)的元素,而另一些則不允許。一些 collection 是有序的,而另一些則是無(wú)序的。JDK 不提供此接口的任何直接 實(shí)現(xiàn):它提供更具體的子接口(如 Set 和 List)實(shí)現(xiàn)。此接口通常用來(lái)傳遞 collection,并在需要最大普遍性的地方操作這些 collection。 collections 此類(lèi)完全由在 collection 上進(jìn)行操作或返回 collection 的靜態(tài)方法組成。它包含在 collection 上操作的多態(tài)算法,即“包裝器”,包裝器返回由指定 collection 支持的新 collection,以及少數(shù)其他內(nèi)容。 如果為此類(lèi)的方法所提供的 collection 或類(lèi)對(duì)象為 null,則這些方法都會(huì)拋出 NullPointerException。
java多線程:
線程或者說(shuō)多線程,是我們處理多任務(wù)的強(qiáng)大工具。線程和進(jìn)程是不同的,每個(gè)進(jìn)程都是一個(gè)獨(dú)立運(yùn)行的程序,擁有自己的變量,且不同進(jìn)程間的變量不能共享;而線程是運(yùn)行在進(jìn)程內(nèi)部的,每個(gè)正在運(yùn)行的進(jìn)程至少有一個(gè)線程,而且不同的線程之間可以在進(jìn)程范圍內(nèi)共享數(shù)據(jù)。也就是說(shuō)進(jìn)程有自己獨(dú)立的存儲(chǔ)空間,而線程是和它所屬的進(jìn)程內(nèi)的其他線程共享一個(gè)存儲(chǔ)空間。線程的使用可以使我們能夠并行地處理一些事情。線程通過(guò)并行的處理給用戶(hù)帶來(lái)更好的使用體驗(yàn),比如你使用的郵件系統(tǒng)(outlook、Thunderbird、foxmail等),你當(dāng)然不希望它們?cè)谑杖⌒锣]件的時(shí)候,導(dǎo)致你連已經(jīng)收下來(lái)的郵件都無(wú)法閱讀,而只能等待收取郵件操作執(zhí)行完畢。這正是線程的意義所在。
實(shí)現(xiàn)線程的方式
實(shí)現(xiàn)線程的方式有兩種: 繼承java.lang.Thread,并重寫(xiě)它的run()方法,將線程的執(zhí)行主體放入其中。 實(shí)現(xiàn)java.lang.Runnable接口,實(shí)現(xiàn)它的run()方法,并將線程的執(zhí)行主體放入其中。
這是繼承Thread類(lèi)實(shí)現(xiàn)線程的示例: [java] view plaincopy public class ThreadTest extends Thread { public void run() { // 在這里編寫(xiě)線程執(zhí)行的主體 // do something } }
這是實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)多線程的示例: [java] view plaincopy public class RunnableTest implements Runnable { public void run() { // 在這里編寫(xiě)線程執(zhí)行的主體 // do something } }
這兩種實(shí)現(xiàn)方式的區(qū)別并不大。繼承Thread類(lèi)的方式實(shí)現(xiàn)起來(lái)較為簡(jiǎn)單,但是繼承它的類(lèi)就不能再繼承別的類(lèi)了,因此也就不能繼承別的類(lèi)的有用的方法了。而使用是想Runnable接口的方式就不存在這個(gè)問(wèn)題了,而且這種實(shí)現(xiàn)方式將線程主體和線程對(duì)象本身分離開(kāi)來(lái),邏輯上也較為清晰,所以推薦大家更多地采用這種方式。
如何啟動(dòng)線程
我們通過(guò)以上兩種方式實(shí)現(xiàn)了一個(gè)線程之后,線程的實(shí)例并沒(méi)有被創(chuàng)建,因此它們也并沒(méi)有被運(yùn)行。我們要啟動(dòng)一個(gè)線程,必須調(diào)用方法來(lái)啟動(dòng)它,這個(gè)方法就是Thread類(lèi)的start()方法,而不是run()方法(既不是我們繼承Thread類(lèi)重寫(xiě)的run()方法,也不是實(shí)現(xiàn)Runnable接口的run()方法)。run()方法中包含的是線程的主體,也就是這個(gè)線程被啟動(dòng)后將要運(yùn)行的代碼,它跟線程的啟動(dòng)沒(méi)有任何關(guān)系。上面兩種實(shí)現(xiàn)線程的方式在啟動(dòng)時(shí)會(huì)有所不同。
繼承Thread類(lèi)的啟動(dòng)方式: [java] view plaincopy public class ThreadStartTest { public static void main(String[] args) { // 創(chuàng)建一個(gè)線程實(shí)例 ThreadTest tt = new ThreadTest(); // 啟動(dòng)線程 tt.start(); } }
實(shí)現(xiàn)Runnable接口的啟動(dòng)方式: [java] view plaincopy public class RunnableStartTest { public static void main(String[] args) { // 創(chuàng)建一個(gè)線程實(shí)例 Thread t = new Thread(new RunnableTest()); // 啟動(dòng)線程 t.start(); } }
實(shí)際上這兩種啟動(dòng)線程的方式原理是一樣的。首先都是調(diào)用本地方法啟動(dòng)一個(gè)線程,其次是在這個(gè)線程里執(zhí)行目標(biāo)對(duì)象的run()方法。那么這個(gè)目標(biāo)對(duì)象是什么呢?為了弄明白這個(gè)問(wèn)題,我們來(lái)看看Thread類(lèi)的run()方法的實(shí)現(xiàn): [java] view plaincopy public void run() { if (target != null) { target.run(); } }
當(dāng)我們采用實(shí)現(xiàn)Runnable接口的方式來(lái)實(shí)現(xiàn)線程的情況下,在調(diào)用new Thread(Runnable target)構(gòu)造器時(shí),將實(shí)現(xiàn)Runnable接口的類(lèi)的實(shí)例設(shè)置成了線程要執(zhí)行的主體所屬的目標(biāo)對(duì)象target,當(dāng)線程啟動(dòng)時(shí),這個(gè)實(shí)例的run()方法就被執(zhí)行了。當(dāng)我們采用繼承Thread的方式實(shí)現(xiàn)線程時(shí),線程的這個(gè)run()方法被重寫(xiě)了,所以當(dāng)線程啟動(dòng)時(shí),執(zhí)行的是這個(gè)對(duì)象自身的run()方法。總結(jié)起來(lái)就一句話,線程類(lèi)有一個(gè)Runnable類(lèi)型的target屬性,它是線程啟動(dòng)后要執(zhí)行的run()方法所屬的主體,如果我們采用的是繼承Thread類(lèi)的方式,那么這個(gè)target就是線程對(duì)象自身,如果我們采用的是實(shí)現(xiàn)Runnable接口的方式,那么這個(gè)target就是實(shí)現(xiàn)了Runnable接口的類(lèi)的實(shí)例。
線程的狀態(tài)
在Java 1.4及以下的版本中,每個(gè)線程都具有新建、可運(yùn)行、阻塞、死亡四種狀態(tài),但是在Java 5.0及以上版本中,線程的狀態(tài)被擴(kuò)充為新建、可運(yùn)行、阻塞、等待、定時(shí)等待、死亡六種。線程的狀態(tài)完全包含了一個(gè)線程從新建到運(yùn)行,最后到結(jié)束的整個(gè)生命周期。線程狀態(tài)的具體信息如下: NEW(新建狀態(tài)、初始化狀態(tài)):線程對(duì)象已經(jīng)被創(chuàng)建,但是還沒(méi)有被啟動(dòng)時(shí)的狀態(tài)。這段時(shí)間就是在我們調(diào)用new命令之后,調(diào)用start()方法之前。 RUNNABLE(可運(yùn)行狀態(tài)、就緒狀態(tài)):在我們調(diào)用了線程的start()方法之后線程所處的狀態(tài)。處于RUNNABLE狀態(tài)的線程在JAVA虛擬機(jī)(JVM)上是運(yùn)行著的,但是它可能還正在等待操作系統(tǒng)分配給它相應(yīng)的運(yùn)行資源以得以運(yùn)行。 BLOCKED(阻塞狀態(tài)、被中斷運(yùn)行):線程正在等待其它的線程釋放同步鎖,以進(jìn)入一個(gè)同步塊或者同步方法繼續(xù)運(yùn)行;或者它已經(jīng)進(jìn)入了某個(gè)同步塊或同步方法,在運(yùn)行的過(guò)程中它調(diào)用了某個(gè)對(duì)象繼承自java.lang.Object的wait()方法,正在等待重新返回這個(gè)同步塊或同步方法。 WAITING(等待狀態(tài)):當(dāng)前線程調(diào)用了java.lang.Object.wait()、java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三個(gè)中的任意一個(gè)方法,正在等待另外一個(gè)線程執(zhí)行某個(gè)操作。比如一個(gè)線程調(diào)用了某個(gè)對(duì)象的wait()方法,正在等待其它線程調(diào)用這個(gè)對(duì)象的notify()或者notifyAll()(這兩個(gè)方法同樣是繼承自O(shè)bject類(lèi))方法來(lái)喚醒它;或者一個(gè)線程調(diào)用了另一個(gè)線程的join()(這個(gè)方法屬于Thread類(lèi))方法,正在等待這個(gè)方法運(yùn)行結(jié)束。 TIMED_WAITING(定時(shí)等待狀態(tài)):當(dāng)前線程調(diào)用了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)四個(gè)方法中的任意一個(gè),進(jìn)入等待狀態(tài),但是與WAITING狀態(tài)不同的是,它有一個(gè)最大等待時(shí)間,即使等待的條件仍然沒(méi)有滿足,只要到了這個(gè)時(shí)間它就會(huì)自動(dòng)醒來(lái)。 TERMINATED(死亡狀態(tài)、終止?fàn)顟B(tài)):線程完成執(zhí)行后的狀態(tài)。線程執(zhí)行完run()方法中的全部代碼,從該方法中退出,進(jìn)入TERMINATED狀態(tài)。還有一種情況是run()在運(yùn)行過(guò)程中拋出了一個(gè)異常,而這個(gè)異常沒(méi)有被程序捕獲,導(dǎo)致這個(gè)線程異常終止進(jìn)入TERMINATED狀態(tài)。
在Java5.0及以上版本中,線程的全部六種狀態(tài)都以枚舉類(lèi)型的形式定義在java.lang.Thread類(lèi)中了,代碼如下: [java] view plaincopy public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
sleep()和wait()的區(qū)別
sleep()方法和wait()方法都成產(chǎn)生讓當(dāng)前運(yùn)行的線程停止運(yùn)行的效果,這是它們的共同點(diǎn)。下面我們來(lái)詳細(xì)說(shuō)說(shuō)它們的不同之處。
sleep()方法是本地方法,屬于Thread類(lèi),它有兩種定義: [java] view plaincopy public static native void sleep(long millis) throws InterruptedException; public static void sleep(long millis, int nanos) throws InterruptedException { //other code }
其中的參數(shù)millis代表毫秒數(shù)(千分之一秒),nanos代表納秒數(shù)(十億分之一秒)。這兩個(gè)方法都可以讓調(diào)用它的線程沉睡(停止運(yùn)行)指定的時(shí)間,到了這個(gè)時(shí)間,線程就會(huì)自動(dòng)醒來(lái),變?yōu)榭蛇\(yùn)行狀態(tài)(RUNNABLE),但這并不表示它馬上就會(huì)被運(yùn)行,因?yàn)榫€程調(diào)度機(jī)制恢復(fù)線程的運(yùn)行也需要時(shí)間。調(diào)用sleep()方法并不會(huì)讓線程釋放它所持有的同步鎖;而且在這期間它也不會(huì)阻礙其它線程的運(yùn)行。上面的連個(gè)方法都聲明拋出一個(gè)InterruptedException類(lèi)型的異常,這是因?yàn)榫€程在sleep()期間,有可能被持有它的引用的其它線程調(diào)用它的interrupt()方法而中斷。中斷一個(gè)線程會(huì)導(dǎo)致一個(gè)InterruptedException異常的產(chǎn)生,如果你的程序不捕獲這個(gè)異常,線程就會(huì)異常終止,進(jìn)入TERMINATED狀態(tài),如果你的程序捕獲了這個(gè)異常,那么程序就會(huì)繼續(xù)執(zhí)行catch語(yǔ)句塊(可能還有finally語(yǔ)句塊)以及以后的代碼。
為了更好地理解interrupt()效果,我們來(lái)看一下下面這個(gè)例子: [java] view plaincopy public class InterruptTest { public static void main(String[] args) { Thread t = new Thread() { public void run() { try { System.out.println("我被執(zhí)行了-在sleep()方法前"); // 停止運(yùn)行10分鐘 Thread.sleep(1000 * 60 * 60 * 10); System.out.println("我被執(zhí)行了-在sleep()方法后"); } catch (InterruptedException e) { System.out.println("我被執(zhí)行了-在catch語(yǔ)句塊中"); } System.out.println("我被執(zhí)行了-在try{}語(yǔ)句塊后"); } }; // 啟動(dòng)線程 t.start(); // 在sleep()結(jié)束前中斷它 t.interrupt(); } }
運(yùn)行結(jié)果: 我被執(zhí)行了-在sleep()方法前 我被執(zhí)行了-在catch語(yǔ)句塊中 我被執(zhí)行了-在try{}語(yǔ)句塊后
wait()方法也是本地方法,屬于Object類(lèi),有三個(gè)定義: [java] view plaincopy 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)方法實(shí)現(xiàn)的。同樣地,timeout代表毫秒數(shù),nanos代表納秒數(shù)。當(dāng)調(diào)用了某個(gè)對(duì)象的wait()方法時(shí),當(dāng)前運(yùn)行的線程就會(huì)轉(zhuǎn)入等待狀態(tài)(WAITING),等待別的線程再次調(diào)用這個(gè)對(duì)象的notify()或者notifyAll()方法(這兩個(gè)方法也是本地方法)喚醒它,或者到了指定的最大等待時(shí)間,線程自動(dòng)醒來(lái)。如果線程擁有某個(gè)或某些對(duì)象的同步鎖,那么在調(diào)用了wait()后,這個(gè)線程就會(huì)釋放它持有的所有同步資源,而不限于這個(gè)被調(diào)用了wait()方法的對(duì)象。wait()方法同樣會(huì)被Thread類(lèi)的interrupt()方法中斷,并產(chǎn)生一個(gè)InterruptedException異常,效果同sleep()方法被中斷一樣。
實(shí)現(xiàn)同步的方式
同步是多線程中的重要概念。同步的使用可以保證在多線程運(yùn)行的環(huán)境中,程序不會(huì)產(chǎn)生設(shè)計(jì)之外的錯(cuò)誤結(jié)果。同步的實(shí)現(xiàn)方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關(guān)鍵字。
給一個(gè)方法增加synchronized修飾符之后就可以使它成為同步方法,這個(gè)方法可以是靜態(tài)方法和非靜態(tài)方法,但是不能是抽象類(lèi)的抽象方法,也不能是接口中的接口方法。下面代碼是一個(gè)同步方法的示例: [java] view plaincopy public synchronized void aMethod() { // do something } public static synchronized void anotherMethod() { // do something }
線程在執(zhí)行同步方法時(shí)是具有排它性的。當(dāng)任意一個(gè)線程進(jìn)入到一個(gè)對(duì)象的任意一個(gè)同步方法時(shí),這個(gè)對(duì)象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問(wèn)這個(gè)對(duì)象的任意一個(gè)同步方法,直到這個(gè)線程執(zhí)行完它所調(diào)用的同步方法并從中退出,從而導(dǎo)致它釋放了該對(duì)象的同步鎖之后。在一個(gè)對(duì)象被某個(gè)線程鎖定之后,其他線程是可以訪問(wèn)這個(gè)對(duì)象的所有非同步方法的。
同步塊的形式雖然與同步方法不同,但是原理和效果是一致的。同步塊是通過(guò)鎖定一個(gè)指定的對(duì)象,來(lái)對(duì)同步塊中包含的代碼進(jìn)行同步;而同步方法是對(duì)這個(gè)方法塊里的代碼進(jìn)行同步,而這種情況下鎖定的對(duì)象就是同步方法所屬的主體對(duì)象自身。如果這個(gè)方法是靜態(tài)同步方法呢?那么線程鎖定的就不是這個(gè)類(lèi)的對(duì)象了,也不是這個(gè)類(lèi)自身,而是這個(gè)類(lèi)對(duì)應(yīng)的java.lang.Class類(lèi)型的對(duì)象。同步方法和同步塊之間的相互制約只限于同一個(gè)對(duì)象之間,所以靜態(tài)同步方法只受它所屬類(lèi)的其它靜態(tài)同步方法的制約,而跟這個(gè)類(lèi)的實(shí)例(對(duì)象)沒(méi)有關(guān)系。
下面這段代碼演示了同步塊的實(shí)現(xiàn)方式: [java] view plaincopy public void test() { // 同步鎖 String lock = "LOCK"; // 同步塊 synchronized (lock) { // do something } int i = 0; // ... }
對(duì)于作為同步鎖的對(duì)象并沒(méi)有什么特別要求,任意一個(gè)對(duì)象都可以。如果一個(gè)對(duì)象既有同步方法,又有同步塊,那么當(dāng)其中任意一個(gè)同步方法或者同步塊被某個(gè)線程執(zhí)行時(shí),這個(gè)對(duì)象就被鎖定了,其他線程無(wú)法在此時(shí)訪問(wèn)這個(gè)對(duì)象的同步方法,也不能執(zhí)行同步塊。
synchronized和Lock
Lock是一個(gè)接口,它位于Java 5.0新增的java.utils.concurrent包的子包locks中。concurrent包及其子包中的類(lèi)都是用來(lái)處理多線程編程的。實(shí)現(xiàn)Lock接口的類(lèi)具有與synchronized關(guān)鍵字同樣的功能,但是它更加強(qiáng)大一些。java.utils.concurrent.locks.ReentrantLock是較常用的實(shí)現(xiàn)了Lock接口的類(lèi)。下面是ReentrantLock類(lèi)的一個(gè)應(yīng)用實(shí)例: [java] view plaincopy private Lock lock = new ReentrantLock(); public void testLock() { // 鎖定對(duì)象 lock.lock(); try { // do something } finally { // 釋放對(duì)對(duì)象的鎖定 lock.unlock(); } }
lock()方法用于鎖定對(duì)象,unlock()方法用于釋放對(duì)對(duì)象的鎖定,他們都是在Lock接口中定義的方法。位于這兩個(gè)方法之間的代碼在被執(zhí)行時(shí),效果等同于被放在synchronized同步塊中。一般用法是將需要在lock()和unlock()方法之間執(zhí)行的代碼放在try{}塊中,并且在finally{}塊中調(diào)用unlock()方法,這樣就可以保證即使在執(zhí)行代碼拋出異常的情況下,對(duì)象的鎖也總是會(huì)被釋放,否則的話就會(huì)為死鎖的產(chǎn)生增加可能。
使用synchronized關(guān)鍵字實(shí)現(xiàn)的同步,會(huì)把一個(gè)對(duì)象的所有同步方法和同步塊看做一個(gè)整體,只要有一個(gè)被某個(gè)線程調(diào)用了,其他的就無(wú)法被別的線程執(zhí)行,即使這些方法或同步塊與被調(diào)用的代碼之間沒(méi)有任何邏輯關(guān)系,這顯然降低了程序的運(yùn)行效率。而使用Lock就能夠很好地解決這個(gè)問(wèn)題。我們可以把一個(gè)對(duì)象中按照邏輯關(guān)系把需要同步的方法或代碼進(jìn)行分組,為每個(gè)組創(chuàng)建一個(gè)Lock類(lèi)型的對(duì)象,對(duì)實(shí)現(xiàn)同步。那么,當(dāng)一個(gè)同步塊被執(zhí)行時(shí),這個(gè)線程只會(huì)鎖定與當(dāng)前運(yùn)行代碼相關(guān)的其他代碼最小集合,而并不影響其他線程對(duì)其余同步代碼的調(diào)用執(zhí)行。
關(guān)于死鎖
死鎖就是一個(gè)進(jìn)程中的每個(gè)線程都在等待這個(gè)進(jìn)程中的其他線程釋放所占用的資源,從而導(dǎo)致所有線程都無(wú)法繼續(xù)執(zhí)行的情況。死鎖是多線程編程中一個(gè)隱藏的陷阱,它經(jīng)常發(fā)生在多個(gè)線程共用資源的時(shí)候。在實(shí)際開(kāi)發(fā)中,死鎖一般隱藏的較深,不容易被發(fā)現(xiàn),一旦死鎖現(xiàn)象發(fā)生,就必然會(huì)導(dǎo)致程序的癱瘓。因此必須避免它的發(fā)生。
程序中必須同時(shí)滿足以下四個(gè)條件才會(huì)引發(fā)死鎖: 互斥(Mutual exclusion):線程所使用的資源中至少有一個(gè)是不能共享的,它在同一時(shí)刻只能由一個(gè)線程使用。 持有與等待(Hold and wait):至少有一個(gè)線程已經(jīng)持有了資源,并且正在等待獲取其他的線程所持有的資源。 非搶占式(No pre-emption):如果一個(gè)線程已經(jīng)持有了某個(gè)資源,那么在這個(gè)線程釋放這個(gè)資源之前,別的線程不能把它搶奪過(guò)去使用。 循環(huán)等待(Circular wait):假設(shè)有N個(gè)線程在運(yùn)行,第一個(gè)線程持有了一個(gè)資源,并且正在等待獲取第二個(gè)線程持有的資源,而第二個(gè)線程正在等待獲取第三個(gè)線程持有的資源,依此類(lèi)推……第N個(gè)線程正在等待獲取第一個(gè)線程持有的資源,由此形成一個(gè)循環(huán)等待。
線程池
線程池就像數(shù)據(jù)庫(kù)連接池一樣,是一個(gè)對(duì)象池。所有的對(duì)象池都有一個(gè)共同的目的,那就是為了提高對(duì)象的使用率,從而達(dá)到提高程序效率的目的。比如對(duì)于Servlet,它被設(shè)計(jì)為多線程的(如果它是單線程的,你就可以想象,當(dāng)1000個(gè)人同時(shí)請(qǐng)求一個(gè)網(wǎng)頁(yè)時(shí),在第一個(gè)人獲得請(qǐng)求結(jié)果之前,其它999個(gè)人都在郁悶地等待),如果為每個(gè)用戶(hù)的每一次請(qǐng)求都創(chuàng)建一個(gè)新的線程對(duì)象來(lái)運(yùn)行的話,系統(tǒng)就會(huì)在創(chuàng)建線程和銷(xiāo)毀線程上耗費(fèi)很大的開(kāi)銷(xiāo),大大降低系統(tǒng)的效率。因此,Servlet多線程機(jī)制背后有一個(gè)線程池在支持,線程池在初始化初期就創(chuàng)建了一定數(shù)量的線程對(duì)象,通過(guò)提高對(duì)這些對(duì)象的利用率,避免高頻率地創(chuàng)建對(duì)象,從而達(dá)到提高程序的效率的目的。
下面實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的線程池,從中理解它的實(shí)現(xiàn)原理。為此我們定義了四個(gè)類(lèi),它們的用途及具體實(shí)現(xiàn)如下: Task(任務(wù)):這是個(gè)代表任務(wù)的抽象類(lèi),其中定義了一個(gè)deal()方法,繼承Task抽象類(lèi)的子類(lèi)需要實(shí)現(xiàn)這個(gè)方法,并把這個(gè)任務(wù)需要完成的具體工作在deal()方法編碼實(shí)現(xiàn)。線程池中的線程之所以被創(chuàng)建,就是為了執(zhí)行各種各樣數(shù)量繁多的任務(wù)的,為了方便線程對(duì)任務(wù)的處理,我們需要用Task抽象類(lèi)來(lái)保證任務(wù)的具體工作統(tǒng)一放在deal()方法里來(lái)完成,這樣也使代碼更加規(guī)范。 Task的定義如下: [java] view plaincopy public abstract class Task { public enum State { /* 新建 */NEW, /* 執(zhí)行中 */RUNNING, /* 已完成 */FINISHED } // 任務(wù)狀態(tài) private State state = State.NEW; public void setState(State state) { this.state = state; } public State getState() { return state; } public abstract void deal(); } TaskQueue(任務(wù)隊(duì)列):在同一時(shí)刻,可能有很多任務(wù)需要執(zhí)行,而程序在同一時(shí)刻只能執(zhí)行一定數(shù)量的任務(wù),當(dāng)需要執(zhí)行的任務(wù)數(shù)超過(guò)了程序所能承受的任務(wù)數(shù)時(shí)怎么辦呢?這就有了先執(zhí)行哪些任務(wù),后執(zhí)行哪些任務(wù)的規(guī)則。TaskQueue類(lèi)就定義了這些規(guī)則中的一種,它采用的是FIFO(先進(jìn)先出,英文名是First In First Out)的方式,也就是按照任務(wù)到達(dá)的先后順序執(zhí)行。 TaskQueue類(lèi)的定義如下: [java] view plaincopy import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class TaskQueue { private List<Task> queue = new LinkedList<Task>(); // 添加一項(xiàng)任務(wù) public synchronized void addTask(Task task) { if (task != null) { queue.add(task); } } // 完成任務(wù)后將它從任務(wù)隊(duì)列中刪除 public synchronized void finishTask(Task task) { if (task != null) { task.setState(Task.State.FINISHED); queue.remove(task); } } // 取得一項(xiàng)待執(zhí)行任務(wù) public synchronized Task getTask() { Iterator<Task> it = queue.iterator(); Task task; while (it.hasNext()) { task = it.next(); // 尋找一個(gè)新建的任務(wù) if (Task.State.NEW.equals(task.getState())) { // 把任務(wù)狀態(tài)置為運(yùn)行中 task.setState(Task.State.RUNNING); return task; } } return null; } }
addTask(Task task)方法用于當(dāng)一個(gè)新的任務(wù)到達(dá)時(shí),將它添加到任務(wù)隊(duì)列中。這里使用了LinkedList類(lèi)來(lái)保存任務(wù)到達(dá)的先后順序。finishTask(Task task)方法用于任務(wù)被執(zhí)行完畢時(shí),將它從任務(wù)隊(duì)列中清除出去。getTask()方法用于取得當(dāng)前要執(zhí)行的任務(wù)。 TaskThread(執(zhí)行任務(wù)的線程):它繼承自Thread類(lèi),專(zhuān)門(mén)用于執(zhí)行任務(wù)隊(duì)列中的待執(zhí)行任務(wù)。 [java] view plaincopy public class TaskThread extends Thread { // 該線程所屬的線程池 private ThreadPoolService service; public TaskThread(ThreadPoolService tps) { service = tps; } public void run() { // 在線程池運(yùn)行的狀態(tài)下執(zhí)行任務(wù)隊(duì)列中的任務(wù) while (service.isRunning()) { TaskQueue queue = service.getTaskQueue(); Task task = queue.getTask(); if (task != null) { task.deal(); } queue.finishTask(task); } } } ThreadPoolService(線程池服務(wù)類(lèi)):這是線程池最核心的一個(gè)類(lèi)。它在被創(chuàng)建了時(shí)候就創(chuàng)建了幾個(gè)線程對(duì)象,但是這些線程并沒(méi)有啟動(dòng)運(yùn)行,但調(diào)用了start()方法啟動(dòng)線程池服務(wù)時(shí),它們才真正運(yùn)行。stop()方法可以停止線程池服務(wù),同時(shí)停止池中所有線程的運(yùn)行。而runTask(Task task)方法是將一個(gè)新的待執(zhí)行任務(wù)交與線程池來(lái)運(yùn)行。 ThreadPoolService類(lèi)的定義如下: [java] view plaincopy import java.util.ArrayList; import java.util.List; public class ThreadPoolService { // 線程數(shù) public static final int THREAD_COUNT = 5; // 線程池狀態(tài) private Status status = Status.NEW; private TaskQueue queue = new TaskQueue(); public enum Status { /* 新建 */NEW, /* 提供服務(wù)中 */RUNNING, /* 停止服務(wù) */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); } } // 啟動(dòng)服務(wù) public void start() { this.status = Status.RUNNING; for (int i = 0; i < THREAD_COUNT; i++) { threads.get(i).start(); } } // 停止服務(wù) public void stop() { this.status = Status.TERMINATED; } // 是否正在運(yùn)行 public boolean isRunning() { return status == Status.RUNNING; } // 執(zhí)行任務(wù) public void runTask(Task task) { queue.addTask(task); } protected TaskQueue getTaskQueue() { return queue; } }
完成了上面四個(gè)類(lèi),我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的線程池。現(xiàn)在我們就可以使用它了,下面的代碼做了一個(gè)簡(jiǎn)單的示例: [java] view plaincopy 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(); // 執(zhí)行十次任務(wù) for (int i = 0; i < 10; i++) { service.runTask(new SimpleTaskTest()); } // 睡眠1秒鐘,等待所有任務(wù)執(zhí)行完畢 Thread.sleep(1000); service.stop(); } }
當(dāng)然,我們實(shí)現(xiàn)的是最簡(jiǎn)單的,這里只是為了演示線程池的實(shí)現(xiàn)原理。在實(shí)際應(yīng)用中,根據(jù)情況的不同,可以做很多優(yōu)化。比如: 調(diào)整任務(wù)隊(duì)列的規(guī)則,給任務(wù)設(shè)置優(yōu)先級(jí),級(jí)別高的任務(wù)優(yōu)先執(zhí)行。 動(dòng)態(tài)維護(hù)線程池,當(dāng)待執(zhí)行任務(wù)數(shù)量較多時(shí),增加線程的數(shù)量,加快任務(wù)的執(zhí)行速度;當(dāng)任務(wù)較少時(shí),回收一部分長(zhǎng)期閑置的線程,減少對(duì)系統(tǒng)資源的消耗。
事實(shí)上Java5.0及以上版本已經(jīng)為我們提供了線程池功能,無(wú)需再重新實(shí)現(xiàn)。這些類(lèi)位于java.util.concurrent包中。
Executors類(lèi)提供了一組創(chuàng)建線程池對(duì)象的方法,常用的有一下幾個(gè): [java] view plaincopy public static ExecutorService newCachedThreadPool() { // other code } public static ExecutorService newFixedThreadPool(int nThreads) { // other code } public static ExecutorService newSingleThreadExecutor() { // other code }
newCachedThreadPool()方法創(chuàng)建一個(gè)動(dòng)態(tài)的線程池,其中線程的數(shù)量會(huì)根據(jù)實(shí)際需要來(lái)創(chuàng)建和回收,適合于執(zhí)行大量短期任務(wù)的情況;newFixedThreadPool(int nThreads)方法創(chuàng)建一個(gè)包含固定數(shù)量線程對(duì)象的線程池,nThreads代表要?jiǎng)?chuàng)建的線程數(shù),如果某個(gè)線程在運(yùn)行的過(guò)程中因?yàn)楫惓6K止了,那么一個(gè)新的線程會(huì)被創(chuàng)建和啟動(dòng)來(lái)代替它;而newSingleThreadExecutor()方法則只在線程池中創(chuàng)建一個(gè)線程,來(lái)執(zhí)行所有的任務(wù)。
這三個(gè)方法都返回了一個(gè)ExecutorService類(lèi)型的對(duì)象。實(shí)際上,ExecutorService是一個(gè)接口,它的submit()方法負(fù)責(zé)接收任務(wù)并交與線程池中的線程去運(yùn)行。submit()方法能夠接受Callable和Runnable兩種類(lèi)型的對(duì)象。它們的用法和區(qū)別如下: Runnable接口:繼承Runnable接口的類(lèi)要實(shí)現(xiàn)它的run()方法,并將執(zhí)行任務(wù)的代碼放入其中,run()方法沒(méi)有返回值。適合于只做某種操作,不關(guān)心運(yùn)行結(jié)果的情況。 Callable接口:繼承Callable接口的類(lèi)要實(shí)現(xiàn)它的call()方法,并將執(zhí)行任務(wù)的代碼放入其中,call()將任務(wù)的執(zhí)行結(jié)果作為返回值。適合于執(zhí)行某種操作后,需要知道執(zhí)行結(jié)果的情況。
無(wú)論是接收Runnable型參數(shù),還是接收Callable型參數(shù)的submit()方法,都會(huì)返回一個(gè)Future(也是一個(gè)接口)類(lèi)型的對(duì)象。該對(duì)象中包含了任務(wù)的執(zhí)行情況以及結(jié)果。調(diào)用Future的boolean isDone()方法可以獲知任務(wù)是否執(zhí)行完畢;調(diào)用Object get()方法可以獲得任務(wù)執(zhí)行后的返回結(jié)果,如果此時(shí)任務(wù)還沒(méi)有執(zhí)行完,get()方法會(huì)保持等待,直到相應(yīng)的任務(wù)執(zhí)行完畢后,才會(huì)將結(jié)果返回。
我們用下面的一個(gè)例子來(lái)演示Java5.0中線程池的使用: [java] view plaincopy 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());// 提交任務(wù) Future fc = es.submit(new CallableTest());// 提交任務(wù) // 取得返回值并輸出 System.out.println((String) fc.get()); // 檢查任務(wù)是否執(zhí)行完畢 if (fr.isDone()) { System.out.println("執(zhí)行完畢-RunnableTest.run()"); } else { System.out.println("未執(zhí)行完-RunnableTest.run()"); } // 檢查任務(wù)是否執(zhí)行完畢 if (fc.isDone()) { System.out.println("執(zhí)行完畢-CallableTest.run()"); } else { System.out.println("未執(zhí)行完-CallableTest.run()"); } // 停止線程池服務(wù) es.shutdown(); } } class RunnableTest implements Runnable { public void run() { System.out.println("已經(jīng)執(zhí)行-RunnableTest.run()"); } } class CallableTest implements Callable { public Object call() { System.out.println("已經(jīng)執(zhí)行-CallableTest.call()"); return "返回值-CallableTest.call()"; } }
運(yùn)行結(jié)果: 已經(jīng)執(zhí)行-RunnableTest.run() 已經(jīng)執(zhí)行-CallableTest.call() 返回值-CallableTest.call() 執(zhí)行完畢-RunnableTest.run() 執(zhí)行完畢-CallableTest.run()
使用完線程池之后,需要調(diào)用它的shutdown()方法停止服務(wù),否則其中的所有線程都會(huì)保持運(yùn)行,程序不會(huì)退出。 |
|