| 時(shí)間:2006-11-21 作者:axman 瀏覽次數: 1271 本文關(guān)鍵字:Java, 多線(xiàn)程, 線(xiàn)程, 線(xiàn)程對象, 單線(xiàn)程, go deep into java |
|

本節繼續上一節的討論。
[一個(gè)線(xiàn)程在進(jìn)入對象的休息室(調用該對象的wait()方法)后會(huì )釋放對該對象的鎖],基于這個(gè)原因。在同步中,除非必要,否則你不應用使用Thread.sleep(long l)方法,因為sleep方法并不釋放對象的鎖。
這是一個(gè)極其惡劣的品德,你自己什么事也不干,進(jìn)入sleep狀態(tài),卻抓住競爭對象的監視鎖不讓其它需要該對象監視鎖的線(xiàn)程運行,簡(jiǎn)單說(shuō)是極端自私的一種行為。但我看到過(guò)很多程序員仍然有在同步方法中調用sleep的代碼。
看下面的例子:
package debug;class SleepTest{public synchronized void wantSleep(){try{Thread.sleep(1000*60);}catch(Exception e){}System.out.println("111");}public synchronized void say(){System.out.println("123");}}class T1 extends Thread{SleepTest st;public T1(SleepTest st){this.st = st;}public void run(){st.wantSleep();}}class T2 extends Thread{SleepTest st;public T2(SleepTest st){this.st = st;}public void run(){st.say();}}public class Test {public static void main(String[] args) throws Exception{SleepTest st = new SleepTest();new T1(st).start();new T2(st).start();}}我們看到,線(xiàn)程T1的實(shí)例運行后,當前線(xiàn)程抓住了st實(shí)例的鎖,然后進(jìn)入了sleep。直到它睡滿(mǎn)60秒后才運行到System.out.println("111");然后run方法運行完成釋放了對st的監視鎖,線(xiàn)程T2的實(shí)例才得到運行的機會(huì )。
而如果我們把wantSleep方法改成:
public synchronized void wantSleep(){try{//Thread.sleep(1000*60);this.wait(1000*60);}catch(Exception e){}System.out.println("111");}我們看到,T2的實(shí)例所在的線(xiàn)程立即就得到了運行機會(huì ),首先打印了123,而T1的實(shí)例所在的線(xiàn)程仍然等待,直到等待60秒后運行到System.out.println("111");方法。
所以,調用wait(long l)方法不僅達到了阻塞當前線(xiàn)程規定時(shí)間內不運行,而且讓其它有競爭需求的線(xiàn)程有了運行機會(huì ),這種利人不損己的方法,何樂(lè )而不為?這也是一個(gè)有良心的程序員應該遵循的原則。
當一個(gè)線(xiàn)程調用wait(long l)方法后,線(xiàn)程如果繼續運行,你無(wú)法知道它是等待時(shí)間完成了還是在wait時(shí)被其它線(xiàn)程喚醒了,如果你非常在意它一定要等待足夠的時(shí)間才執行某任務(wù),而不希望是中途被喚醒,這里有一個(gè)不是非常準確的方法:
long l = System.System.currentTimeMillis();wait(1000);//準備讓當前線(xiàn)程等待1秒while((System.System.currentTimeMillis() - l) < 1000)//執行到這里說(shuō)明它還沒(méi)有等待到1秒//是讓其它線(xiàn)程給鬧醒了wait(1000-(System.System.currentTimeMillis()-l));//繼續等待余下的時(shí)間.
這種方法不是很準確,但基本上能達到目的。
所以在同步方法中,除非你明確知道自己在干什么,非要這么做的話(huà),你沒(méi)有理由使用sleep,wait方法足夠達到你想要的目的。而如果你是一個(gè)很保守的人,看到上面這段話(huà)后,你對sleep方法深?lèi)和唇^,堅決不用sleep了,那么在非同步的方法中(沒(méi)有和其它線(xiàn)程競爭的對象),你想讓當前線(xiàn)程阻塞一定時(shí)間后再運行,應該如何做呢?(這完全是一種賣(mài)弄,在非同步的方法中你就應該合理地應用sleep嘛,但如果你堅決不用sleep,那就這樣來(lái)做吧)
public static mySleep(long l){Object o = new Object();synchronized(o){try{o.wait(l);}catch(Exception e){}}}放心吧,沒(méi)有人能在這個(gè)方法外調用o.notify[All],所以o.wait(l)會(huì )一直等到設定的時(shí)間才會(huì )運行完成。
虛擬鎖簡(jiǎn)單說(shuō)就是不要調用synchronized方法(它等同于synchronized(this))和不要調用synchronized(this),這樣所有調用在這個(gè)實(shí)例上的所有同步方法的線(xiàn)程只能有一個(gè)線(xiàn)程可以運行。也就是說(shuō):
如果一個(gè)類(lèi)有兩個(gè)同步方法 m1,m2,那么不僅是兩個(gè)以上線(xiàn)調用m1方法的線(xiàn)程只有一個(gè)能運行,就是兩個(gè)分別調用m1,m2的線(xiàn)程也只有一個(gè)能運行。當然非同步方法不存在任何競爭,在一個(gè)線(xiàn)程獲取該對象的監視鎖后這個(gè)對象的非同步方法可以被任何線(xiàn)程調用。
而大多數時(shí)候,我們可能會(huì )出現這種情況,多個(gè)線(xiàn)程調用m1時(shí)需要保護一種資源,而多個(gè)線(xiàn)程調用M2時(shí)要保護的是另一種資源,如果我們把m1,m2都設成同步方法。兩個(gè)分別調用這兩個(gè)方法的線(xiàn)程其實(shí)并不產(chǎn)生沖突,但它們都要獲取這個(gè)實(shí)例的鎖(同步方法是同步this)而產(chǎn)生了不必要競爭。
所以這里應該采用虛擬鎖。
即將m1和m2方法中各自保護的對象作為屬性a1,a2傳進(jìn)來(lái),然后將同步方法改為方法的同步塊分別以a1,a2為參數,這樣到少是不同線(xiàn)程調用這兩個(gè)不同方法時(shí)不會(huì )產(chǎn)生競爭,當然如果m1,m2方法都操作同一受保護對象則兩個(gè)方法還是應該作為同步方法。這也是應該將方法同步還是采用同步塊的理由之一。
package debug;class SleepTest{public synchronized void m1(){System.out.println("111");try{Thread.sleep(10000);}catch(Exception e){}}public synchronized void m2(){System.out.println("123");}}class T1 extends Thread{SleepTest st;public T1(SleepTest st){this.st = st;}public void run(){st.m1();}}class T2 extends Thread{SleepTest st;public T2(SleepTest st){this.st = st;}public void run(){st.m2();}}public class Test {public static void main(String[] args) throws Exception{SleepTest st = new SleepTest();new T1(st).start();new T2(st).start();}}這個(gè)例子可以看到兩個(gè)線(xiàn)程分別調用st實(shí)例的m1和m2方法卻因為都要獲取st的監視鎖而產(chǎn)生了競爭。T2實(shí)例要在T1運行完成后才能運行(間隔了10秒)。而假設m1方法要操作操作一個(gè)文件 f1,m2方法要操作一個(gè)文件f2,當然我們可以在方法中分別同步f1,f2,但現在還不知道f2,f2是否存在,如果不存在我們就同步了一個(gè)null對象,那么我們可以使用虛擬鎖:
package debug;class SleepTest{String vLock1 = "vLock1";String vLock2 = "vLock2";public void m1(){synchronized(vLock1){System.out.println("111");try {Thread.sleep(10000);}catch (Exception e) {}//操作f1}}public void m2(){synchronized(vLock2){System.out.println("123");//操作f2}}}class T1 extends Thread{SleepTest st;public T1(SleepTest st){this.st = st;}public void run(){st.m1();}}class T2 extends Thread{SleepTest st;public T2(SleepTest st){this.st = st;}public void run(){st.m2();}}public class Test {public static void main(String[] args) throws Exception{SleepTest st = new SleepTest();new T1(st).start();new T2(st).start();}}我們看到兩個(gè)分別調用m1和m2的線(xiàn)程由于它們獲取不同對象的監視鎖,它們沒(méi)有任何競爭就正常運行,只有這兩個(gè)線(xiàn)程同時(shí)調用m1或m2才會(huì )產(chǎn)生阻塞。
轉載自dev2dev網(wǎng)友axman的go deep into java專(zhuān)欄。
一個(gè)男人.
一個(gè)寫(xiě)程序的男人.
一個(gè)寫(xiě)程序并正在從程序中尋找快樂(lè )的男人.
一個(gè)寫(xiě)程序并正在從程序中尋找快樂(lè )并把快樂(lè )傳遞給大家的男人.
一個(gè)書(shū)生.
一個(gè)寂寞的書(shū)生.
一個(gè)寂寞的梅香竹影下敲聲寫(xiě)韻的書(shū)生.
一個(gè)寂寞的梅香竹影下敲聲寫(xiě)韻晨鐘暮鼓中逸氣揚劍的書(shū)生.
那個(gè)男人是位書(shū)生。沒(méi)有人知道他的姓名,居無(wú)定所,行無(wú)定蹤,亦耕變讀,或漁或樵。
所以有人叫他樵夫(Axman),有人叫他漁郎(fisher)。
聯(lián)系客服