讓我們繼續(xù)前面有關(guān)ReentrantLock的話題。
首先,ReentrantLock有一個帶布爾型參數(shù)的構(gòu)造函數(shù),在JDK官方文檔中對它是這樣描述的:
“此類的構(gòu)造方法接受一個可選的公平 參數(shù)。當(dāng)設(shè)置為 true 時,在多個線程的爭用下,這些鎖傾向于將訪問權(quán)授予等待時間最長的線程。否則此鎖將無法保證任何特定訪問順序。與采用默認(rèn)設(shè)置(使用不公平鎖)相比,使用公平鎖的程序在許多線程訪問時表現(xiàn)為很低的總體吞吐量(即速度很慢,常常極其慢),但是在獲得鎖和保證鎖分配的均衡性時差異較小。不過要注意的是,公平鎖不能保證線程調(diào)度的公平性。因此,使用公平鎖的眾多線程中的一員可能獲得多倍的成功機會,這種情況發(fā)生在其他活動線程沒有被處理并且目前并未持有鎖時。還要注意的是,未定時的 tryLock 方法并沒有使用公平設(shè)置。因為即使其他線程正在等待,只要該鎖是可用的,此方法就可以獲得成功。 ”
簡單來講:公平鎖使線程按照請求鎖的順序依次獲得鎖;而不公平鎖則允許討價還價,在這種情況下,線程有時可以比先請求鎖的其他線程先得到鎖。
觀察采用公平鎖和非公平鎖的例程運行效果發(fā)現(xiàn):線程獲得鎖的順序發(fā)生了一些變化(見下表)。
Unfair:
1 is running!
1 got lock1@Step1!
3 is running!
2 is running!
1 first Reading count:1
1 release lock1@Step1!
3 got lock1@Step1!
1 got lock2@Step2!
thread 1 set age to:18
thread 1 first read age
is:18
3 first Reading count:2
3 release lock1@Step1!
2 got lock1@Step1!
thread 1 second read age
is:18
1 release lock2@Step2!
3 got lock2@Step2!
thread 3 set age to:34
thread 3 first read age
is:34
2 first Reading count:3
2 release lock1@Step1!
thread 3 second read age
is:34
3 release lock2@Step2!
2 got lock2@Step2!
thread 2 set age to:72
thread 2 first read age
is:72
thread 2 second read age
is:72
2 release lock2@Step2!
成功生成(總時間:20
秒)
|
Fair:
1 is running!
1 got lock1@Step1!
2 is running!
3 is running!
1 first Reading count:1
1 release lock1@Step1!
1 got lock2@Step2!
thread 1 set age to:82
thread 1 first read age
is:82
2 got lock1@Step1!
2 first Reading count:2
2 release lock1@Step1!
3 got lock1@Step1!
thread 1 second read age
is:82
1 release lock2@Step2!
2 got lock2@Step2!
thread 2 set age to:65
thread 2 first read age
is:65
3 first Reading count:3
3 release lock1@Step1!
thread 2 second read age
is:65
2 release lock2@Step2!
3 got lock2@Step2!
thread 3 set age to:31
thread 3 first read age
is:31
thread 3 second read age
is:31
3 release lock2@Step2!
成功生成(總時間:20
秒)
|
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
這樣的變化告訴我們:
采用非公平的鎖時,當(dāng)一個線程釋放了第一個鎖以后,由于線程的搶占,剛剛被釋放的鎖馬上被下一個線程占有。采用公平鎖時,由于公平鎖傾向于將訪問權(quán)授予等待時間最長的線程,所以,當(dāng)?shù)谝粋€鎖被第一個線程釋放以后,第二個鎖馬上將訪問權(quán)授予第一個線程,而第一個鎖將訪問權(quán)授予了第二個線程。這里,公平鎖在平衡分配方面耗費了一定的時間,這使得第一個線程獲得第二個鎖的時間優(yōu)先于第二個線程獲得第一個鎖。這樣,采用不同的鎖,就出現(xiàn)了兩種不同的結(jié)果。
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
為了看到公平鎖和非公平鎖性能上的差異,我們不妨將其中線程的睡眠時間設(shè)定為1毫秒,然后把循環(huán)產(chǎn)生的線程數(shù)提高到5000(修改后的代碼已忽略,自行修改),可以發(fā)現(xiàn),由于公平鎖要維持鎖分配的均衡性,所以,采用公平鎖的程序總運行時間更長一些。
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
根據(jù)運行環(huán)境的差異,有些朋友可能并不一定能很直觀的從運行結(jié)果中看到兩種不同的鎖帶來的性能差異。不妨引用IBM開發(fā)者社區(qū)的一組測試結(jié)果來看一看就行有什么樣的差異吧:
4CPU情況下的同步、非公平鎖和公平鎖吞吐量比較:

單CPU情況下,同步、非公平鎖和公平鎖的吞吐量:
可以看到,同步和公平鎖的吞吐量都是最低的,公平鎖更低一些。但是同步內(nèi)置的監(jiān)控器鎖是不公平的,而且永遠(yuǎn)都是不公平的。而JVM
保證了所有線程最終都會得到它們所等候的鎖。確保統(tǒng)計上的公平性,對多數(shù)情況來說,這就已經(jīng)足夠了,而這花費的成本則要比絕對的公平保證的低得多。
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
既然Lock這么近乎完美,那我們也許可以忘卻synchronized了。
但是任何事物都是有兩面性的。
1.使用Lock,你必須手動的在finally塊中釋放鎖。鎖的獲得和釋放是不受JVM控制的。這要求編程人員更加細(xì)心。
2.當(dāng) JVM 用 synchronized 管理鎖定請求和釋放時,JVM
在生成線程轉(zhuǎn)儲時能夠包括鎖定信息。這些對調(diào)試非常有價值,因為它們能標(biāo)識死鎖或者其他異常行為的來源。
Lock
類只是普通的類,JVM 不知道具體哪個線程擁有
Lock
對象。
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
Lock提供了在多線程爭用的情況下更好的并發(fā)性,但這是以犧牲一定的可維護(hù)性為代價的。
轉(zhuǎn)載注明出處:http://x-
spirit.javaeye.com/、http:
//www.tkk7.com/zhangwei217245/
所以說,當(dāng)大量線程發(fā)生爭用的時候,Lock來了,大家都讓開。