實踐46
同步機制鎖定的是對象,而不是函數或代碼。當synchronized被當作函數修飾符時,它所取得的lock被交給函數的調用者。如果synchronized用於object reference,則取得的lock交給該reference所指的對象。(因此同一類2個不同對象之間加this鎖是無效的,因爲不是同一個鎖)
Java不允許將構造函數聲明爲synchronized。當兩個線程併發調用一個構造函數的時候,它們各自操控的是同一個class的兩個不同實體的內存。但如果構造函數內部包含競爭共享資源的代碼,則必須同步控制以迴避衝突。
實踐47
當調用一個synchronized static函數時,獲得的lock將與[定義該函數]之class的Class對象相關聯,而不是與調用函數的那個對象相關聯。當對一個class literal調用其synchronized 區段時,獲得的也是同樣那個lock,也就是[與特定Class 對象相關聯]的lock
看一段代碼:
class Foo implements Runnable{
public synchronized void printM1(){
while(true)
System.out.println("M1");
}
public synchronized static void printM2(){
while(true)
System.out.println("M2");
}
public void run(){
printM1();
}
}
class Test{
public static void main(String args[]){
Foo f=new Foo();
Thread t=new Thread(f);
t.start();
f.printM2;
}
}
這段代碼最終較差打印了M1和M2 而沒有實現同步
原因在於:一個同步的是static函數,而一個是instance函數,printM1取得的是Foo object lock,而printM2取得的則是Foo的Class object lock。
要使上述代碼同步可以:1 同步控制公共資源 2 同步控制一個特殊的instance變量
用byte[] lock=new byte[0];是最經濟的
實踐48
對於[在synchronized函數中可被修改的數據],應使之成爲private,並根據需要提供訪問函數。如果訪問函數返回的是可變對象(mutable object),那麼應該先cloned(克隆)該對象。
實踐49
一般情況下請不要同步化所有函數,同步化不僅造成程序緩慢,並且喪失了併發可能
採用“單對象多鎖”技術以允許更多並發動作。
實踐50
不可分割的操作並不意味着多線程安全。只要多個線程共享某些變量,它們就必須被訪問於synchronized函數或區段內,或是被聲明爲volatile。這樣可以確保變量與主內存完全保持一致,從而在任何時刻都得到正確數值。
採用synchronized或是volatile取決於多個因素。如果併發性很紅藥,且不需要更新很多變量,可以使用volatile,如果要更新許多變量,volatile執行速度會比同步低。如果使用synchronized,只有在取得和釋放lock的時候,變量和主內存才進行一致化
兩者關係:
synchronized
優點:取得和釋放lock時進行私有專用副本和主內存正本的一致化
缺點:清除了併發性的可能
volatile
優點:允許併發性
缺點:每次訪問變量就會進行私有專用內存對應主內存的一致化
實踐51
同步化某一函數,並不一定就會使其成爲“多線程安全”,如果synchronized函數操控着多個函數,而它們並不都是此函數所屬class的private instance data,那麼你必須對這些對象自身也進行同步化。
對關鍵詞synchronized必須記住,它鎖定的是對象而非函數或代碼。
實踐52
以固定而全局性的順序取得多個locks(機鎖)以避免死鎖
實踐53
優先使用notifyAll()而非notify()
notify()只喚醒一個線程 而你無法控制喚醒哪一個線程 只有在2個前提下 用notify纔是安全的:
1 只有一個線程在等待
2 多個線程正等待同一條件成立,且哪個被喚醒都無所謂
線程式的優先權(priority)不能確保線程一定被notify()喚醒,也不能確定各線程被notifyAll()以何種順序喚醒。
實踐54
針對wait()和notifyAll()使用旋鎖(spin locks)
由於被喚醒的線程會從之前調用wait()的地點開始繼續向下執行,英雌在等待條件變量時,請總是使用旋鎖確保正確結果。如下:
if(condition==true){
try{
a.wait()
}catch(){}
}
改成
while(condition==true){
try{
a.wait()
}catch(){}
}
從而保證喚醒後條件仍然成立沒有被改變
實踐55
使用wait(),notifyAll()代替輪詢
實踐56
當一個對象被鎖定,有可能其他線程會因同一個object lock而受阻(blocked),假如你對上鎖對象的object reference重新賦值,其他線程內懸而未決的那些locks將不再有意義。
所以,不要對上鎖對象的object reference重新賦值
實踐57
不要調用stop()和suspend()方法
實踐58
通過線程間協作來終止線程
private volatile boolean stop;
public void stopThread(){
stop=true;
}
public void run(){
while(!stop){}
//do Clean Works...
}
很可惜這裏必須用到輪詢 並且注意stop變量被聲明爲volatile以確保值永遠是最新的