Practicle Java筆記 實踐46-58(同步&線程部分)

 

實踐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以確保值永遠是最新的

 

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