打個比方:一個object就像一棟大房子,大門永遠打開。房子裡有很多房間(也就是方法)。這些房間有上鎖的(synchronized方法), 和不上鎖之分(普通方法)。房門口放著一把鑰匙(key),這把鑰匙可以打開所有上鎖的房間。另外我把所有想呼叫該物件方法的線程比喻成想進入這房子某個房間的人。所有的東西就這麼多了,下面我們來看看這些東西之間如何運作的。
在此我們先來明確一下我們的前提條件。該物件至少有一個synchronized方法,否則這個key還有啥意義。當然也就不會有我們的主題了。
一個人想進入某間上了鎖的房間,他來到房子門口,看見鑰匙在那兒(說明暫時還沒有其他人要使用上鎖的房間)。於是他走上去拿到了鑰匙,並且按照自己的計畫使用那些房間。注意一點,他每次使用完一次上鎖的房間後會馬上把鑰匙還回去。即使他要連續使用兩間上鎖的房間,中間他也要把鑰匙還回去,再拿回來。
因此,普通情況下鑰匙的使用原則是:“隨用隨借,用完即還。”
這時其他人可以不受限制的使用那些不上鎖的房間,一個人用一間可以,兩個人用一間也可以,沒限制。但是如果當某個人想要進入上鎖的房間,他就要跑到大門口去看看了。有鑰匙當然拿了就走,沒有的話,就只能等了。
要是很多人在等這把鑰匙,等鑰匙回來以後,誰會優先得到鑰匙? Not guaranteed。象前面例子裡那個想連續使用兩個上鎖房間的傢伙,他中間還鑰匙的時候如果還有其他人在等鑰匙,那麼沒有任何保證這傢伙能再次拿到。 (JAVA規範在很多地方都明確說明不保證,象Thread.sleep()休息後多久會返回運行,相同優先權的線程那個首先被執行,當要訪問對象的鎖被釋放後處於等待池的多個執行緒哪個會優先得到,等等。判斷條件太多,如果說出來可能會影響JAVA的推廣,也可能是因為智慧財產權保護的原因吧。即使看起來很隨機的現象,其實都是有規律可尋。費事,也沒多大意義,所以不確定就不確定了吧。
再來看看同步程式碼區塊。和同步方法有小小的不同。
1.從尺寸上講,同步程式碼區塊比同步方法小。你可以把同步程式碼區塊看成是沒上鎖房間裡的一塊用帶鎖的屏風隔開的空間。
2.同步程式碼區塊還可以人為的指定取得某個其它物件的key。就像是指定要用哪一把鑰匙才能開這個屏風的鎖,你可以用本房的鑰匙;你也可以指定用另一個房子的鑰匙才能開,這樣的話,你要跑到另一棟房子那裡把那個鑰匙拿來,並用那個房子的鑰匙來打開這個房子的帶鎖的屏風。
記住你獲得的那另一棟房子的鑰匙,並不影響其他人進入那棟房子沒有鎖的房間。
為什麼要使用同步程式碼區塊呢?我想應該是這樣的:首先對程式來講同步的部分很影響運行效率,而一個方法通常是先創建一些局部變量,再對這些變量做一些操作,如運算,顯示等等;而同步所覆蓋的程式碼越多,對效率的影響就越嚴重。因此我們通常盡量縮小其影響範圍。如何做?同步程式碼區塊。我們只把一個方法中該同步的地方同步,例如運算。
另外,同步代碼塊可以指定鑰匙這一特點有個額外的好處,是可以在一定時期內霸占某個對象的key。還記得前面說過普通情況下鑰匙的使用原則嗎。現在不是普通情況了。你所取得的那把鑰匙不是永遠不還,而是在退出同步程式碼區塊時才還。
還用前面那個想連續用兩個上鎖房間的傢伙打比方。怎樣才能在用完一間以後,繼續使用另一間。用同步程式碼區塊吧。先創建另外一個線程,做一個同步程式碼區塊,把那個程式碼區塊的鎖指向這個房子的鑰匙。然後啟動那個線程。只要你能在進入那個代碼塊時抓到這房子的鑰匙,你就可以一直保留到退出那個代碼塊。也就是說你甚至可以對本房內所有上鎖的房間遍歷,甚至再sleep(10*60*1000),而房門口卻還有1000個線程在等這把鑰匙呢。很過癮吧。
在此對sleep()方法和鑰匙的關聯性講一下。一個執行緒拿到key後,且沒有完成同步的內容時,如果被強制sleep()了,那key還一直在它那裡。直到它再次運行,做完所有同步內容,才會歸還key。記住,那傢伙只是乾活幹累了,去休息一下,他並沒幹完他要幹的事。為了避免別人進入那個房間把裡面搞的一團糟,即使在睡覺的時候他也要把那唯一的鑰匙戴在身上。
最後,也許有人會問,為什麼要一把鑰匙通開,而不是一把鑰匙一扇門呢?我想這純粹是因為複雜性問題。一個鑰匙一個門當然比較安全,但是會牽扯好多問題。鑰匙的產生,保管,獲得,歸還等等。其複雜度有可能隨同步方法的增加呈幾何級數增加,嚴重影響效率。這也算是權衡的問題吧。為了增加一點點安全性,導致效率大大降低,是多麼不可取。