多線程編程 實戰篇 (二)

實戰篇(二)

本節繼續上一節的討論.
[一個線程在進入對象的休息室(調用該對象的wait()方法)後會釋放對該對象的鎖],基於這個原因.
在同步中,除非必要,否則你不應用使用Thread.sleep(long l)方法,因爲sleep方法並不釋放對象的鎖.

這是一個極其惡劣的品德,你自己什麼事也不幹,進入sleep狀態,卻抓住競爭對象的監視鎖不讓其它需
要該對象監視鎖的線程運行,簡單說是極端自私的一種行爲.但我看到過很多程序員仍然有在同步方法
中調用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();
}
}

我們看到,線程T1的實例運行後,當前線程抓住了st實例的鎖,然後進入了sleep.直到它睡滿60秒後
才運行到System.out.println(“111”);然後run方法運行完成釋放了對st的監視鎖,線程T2的實例才
得到運行的機會.

而如果我們把wantSleep方法改成:
public synchronized void wantSleep(){
try{
//Thread.sleep(1000*60);
this.wait(1000*60);
}catch(Exception e){}
System.out.println(“111”);
}

我們看到,T2的實例所在的線程立即就得到了運行機會,首先打印了123,而T1的實例所在的線程仍然
等待,直到等待60秒後運行到System.out.println(“111”);方法.

所以,調用wait(long l)方法不僅達到了阻塞當前線程規定時間內不運行,而且讓其它有競爭需求的線程
有了運行機會,這種利人不損己的方法,何樂而不爲?這也是一個有良心的程序員應該遵循的原則.

當一個線程調用wait(long l)方法後,線程如果繼續運行,你無法知道它是等待時間完成了還是在wait時被其
它線程喚醒了,如果你非常在意它一定要等待足夠的時間才執行某任務,而不希望是中途被喚醒,這裏有
一個不是非常準確的方法:

long l = System.System.currentTimeMillis();

wait(1000);//準備讓當前線程等待1秒
while((System.System.currentTimeMillis() - l) <1000)//執行到這裏說明它還沒有等待到1秒
//是讓其它線程給鬧醒了

wait(1000-(System.System.currentTimeMillis()-l));//繼續等待餘下的時間.

這種方法不是很準確,但基本上能達到目的.

所以在同步方法中,除非你明確知道自己在幹什麼,非要這麼做的話,你沒有理由使用sleep,wait方法足夠
達到你想要的目的.而如果你是一個很保守的人,看到上面這段話後,你對sleep方法深惡痛絕,堅決不用sleep
了,那麼在非同步的方法中(沒有和其它線程競爭的對象),你想讓當前線程阻塞一定時間後再運行,應該如何
做呢?(這完全是一種賣弄,在非同步的方法中你就應該合理地應用sleep嘛,但如果你堅決不用sleep,那就這
樣來做吧)

public static mySleep(long l){
    Object o = new Object();
    synchronized(o){
        try{
            o.wait(l);    
        }catch(Exception e){}
    }
}

放心吧,沒有人能在這個方法外調用o.notify[All],所以o.wait(l)會一直等到設定的時間纔會運行完成.

[虛擬鎖的使用]

虛擬鎖簡單說就是不要調用synchronized方法(它等同於synchronized(this))和不要調用

synchronized(this),這樣所有調用在這個實例上的所有同步方法的線程只能有一個線程可以運行.也就是說

如果一個類有兩個同步方法 m1,m2,那麼不僅是兩個以上線調用m1方法的線程只有一個能運行,就是兩個分別
調用m1,m2的線程也只有一個能運行.當然非同步方法不存在任何競爭,在一個線程獲取該對象的監視鎖後這個
對象的非同步方法可以被任何線程調用.

而大多數時候,我們可能會出現這種情況,多個線程調用m1時需要保護一種資源,而多個線程調用M2時要保護的
是另一種資源,如果我們把m1,m2都設成同步方法.兩個分別調用這兩個方法的線程其實並不產生衝突,但它們都
要獲取這個實例的鎖(同步方法是同步this)而產生了不必要競爭.

所以這裏應該採用虛擬鎖.
即將m1和m2方法中各自保護的對象作爲屬性a1,a2傳進來,然後將同步方法改爲方法同的同步塊分別以a1,a2爲
參數,這樣到少是不同線程調用這兩個不同方法時不會產生競爭,當然如果m1,m2方法都操作同一受保護對象則
兩個方法還是應該作爲同步方法.這也是應該將方法同步還是採用同步塊的理由之一.

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();
}
}

這個例子可以看到兩個線程分別調用st實例的m1和m2方法卻因爲都要獲取st的監視鎖而產生了競爭.
T2實例要在T1運行完成後才能運行(間隔了10秒)

而假設m1方法要操作操作一個文件 f1,m2方法要操作一個文件f2,當然我們可以在方法中分別同步f1,
f2,但現在還不知道f2,f2是否存在,如果不存在我們就同步了一個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();
}
}
我們看到兩個分別調用m1和m2的線程由於它們獲取不同對象的監視鎖,它們沒有任何競爭就正常運行,
只有這兩個線程同時調用m1或m2纔會產生阻塞.

原文作者:axman
原文出處:http://blog.csdn.net/axman/article/details/431802
個人覺得此博主寫得文章非常好,推薦大家翻閱。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章