Java8并發(fā)教程-Synchronization and Locks

這是該系列教程的第二篇.其中用到了一個工具類,和其中的兩個方法.如下圖所示:

Synchronized

在第一篇教程中,我們已經(jīng)介紹了如何通過** Executor Service**來并行執(zhí)行任務(wù).但是,這也引入了一個新的問題.即我們?nèi)绾尾l(fā)的訪問那些共享的變量.假設(shè)我們打算用多個線程來并發(fā)地增加一個數(shù)字.我們使用下面的代碼:

我們可以看到,其結(jié)果不是正確的結(jié)果,即10000.那為什么會出現(xiàn)這種情況呢?這是因為我們不恰當?shù)淖尪鄠€線程并發(fā)的訪問并設(shè)置共享變量而造成的.本例中,共享變量即為** count**.

當執(zhí)行一個加法操作時,需要按三步進行:1.讀取當前值2.使當前值加一3.將這個新值寫入到變量中.如果有兩個線程同時執(zhí)行第一步,即讀取當前值,它們獲得了相同的值,就會造成寫丟失.也就是說,第二個線程在執(zhí)行第三步時,會覆蓋掉第一個進程寫入的結(jié)果.

Java中,我們可以通過使用** synchronized**關(guān)鍵字,來防止這種情況的出現(xiàn).

我們使用synchronized關(guān)鍵字來改寫上面的執(zhí)行加法操作的函數(shù):

現(xiàn)在我們讓線程通過執(zhí)行這個函數(shù)來并發(fā)地進行加法操作:

現(xiàn)在我們可以看到,結(jié)果是正確的.不管你執(zhí)行多少次,結(jié)果都是正確的.

** synchronized**關(guān)鍵字,不僅可以用在函數(shù)上,還可以用在代碼塊中:

在** synchronized關(guān)鍵字的內(nèi)部, Java使用一個叫做 monitor的東西來管理它, monitor也被稱作 monitor lock 或 intrinsic lock. monitor是和對象關(guān)聯(lián)在一起的,每個 synchronized的方法,針對一個對象,都使用同一個monitor**.

** synchronized**也有可重入的特性,也就是說,即使當前線程已經(jīng)占有了鎖,它還是可以請求相同的鎖的,這就避免了死鎖的產(chǎn)生.

Locks

除了通過使用** synchronized**這種隱式鎖來進行同步,Concurrency API中,還提供了大量顯式鎖.通過使用這些顯式鎖,我們能對并發(fā)進行更好的控制.

下面我們會一個個的介紹這些顯式鎖.

ReentrantLock

這個鎖是一個互斥鎖.它也實現(xiàn)了** synchronized**中的隱式鎖的基本特性,當然,它還是有一些自己的特性的.這個鎖也是可重入的.

我們使用** ReentrantLock**來實現(xiàn)上面的例子:

我們通過** lock()方法來獲得鎖,而通過 unlock()**方法,來釋放鎖.我們要用try/catch來包裝我們的代碼,來防止鎖得不到釋放.這個函數(shù)也是線程安全的.如果其他線程在這個鎖沒有釋放之前,想要獲得鎖,就會被阻塞.只有一個線程能夠同時占有鎖.

除此之外,鎖還提供了其他的函數(shù),如下圖所示:

第一個任務(wù)獲得鎖,然后暫停一秒鐘.而第二個任務(wù)則獲取鎖的當前狀態(tài),并將其輸出出來.

** tryLock()會嘗試獲取鎖,而不會像 lock()**方法一樣,阻塞線程.我們需要在執(zhí)行那些需要訪問共享變量的操作之前,檢查一下其返回值,以防止不同步的情況.

ReadWriteLock

** ReadWriteLock包含一對鎖,分別用于對共享變量的讀和寫操作. ReadWriteLock**背后的原理是,如果當前沒有線程來修改共享變量,那么允許多個線程來訪問共享變量,而沒有什么危險.所以說,當沒有線程持有寫鎖的時候,可以有多個線程持有讀鎖.這在那些讀操作遠大于寫操作的場景中,極大的提高了性能和吞吐量.

在你執(zhí)行上面的代碼時,你會注意到,只有當寫鎖被釋放之后,后面的線程才會同時獲取到讀鎖.而不需要等第一個線程的讀鎖釋放之后,第二個線程才能獲取讀鎖.

StampedLock

Java8中,還增加了一種叫做** StampedLock的鎖,這個鎖也有讀鎖和寫鎖,就跟上面的 ReadWriteLock一樣.但是,和 ReadWriteLock不同,它會返回一個 long**類型的值,我們可以通過這個值來釋放鎖,檢查鎖是否有效.另外,它還包含一種叫做樂觀鎖的鎖.

我們有下面的代碼:

我們通過** readLock()方法來獲取讀鎖,通過 writeLock()方法來獲取寫鎖.需要注意的是, StampedLock**并沒有可重入的特性.如果沒有鎖可用,則調(diào)用上面的方法,會導致返回一個值,并阻塞線程,即使當前線程已經(jīng)有鎖了.所以你使用這個鎖的時候,要小心,別出現(xiàn)死鎖的情況.

跟** ReadWriteLock**鎖一樣,要獲得讀鎖,必須等待寫鎖被釋放.

我們使用下面的這個例子,來了解樂觀鎖:

** tryOptimisticRead()方法,會獲得一個樂觀讀鎖.它總會返回一個值,而不會阻塞當前線程.如果有線程持有寫鎖,則返回值是0.所以,我們需要通過 lock.validate()**方法來檢查一下返回值,來確定是否真的有讀鎖可用.

上面的代碼,輸出如下:

我們應(yīng)當在獲取到樂觀鎖之后,就立即使用它.因為它隨時可能無效.與平常的讀鎖不同,樂觀鎖不會阻止其他的線程獲得寫鎖.也就是說,在一個線程占有樂觀鎖的時候,其他的線程還是可以獲取到寫鎖的,而不需要等待樂觀鎖被釋放.當其他線程獲得了寫鎖之后,樂觀鎖就失效了.即使那個線程后來又釋放了寫鎖.

所以,如果使用樂觀鎖的話,我們需要時刻驗證樂觀鎖是否還有效.特別是在執(zhí)行對共享變量的寫操作之前.

有時,我們需要將一個讀鎖轉(zhuǎn)換成寫鎖.** StampedLock提供了 tryConvertToWriteLock()**這個方法,來進行轉(zhuǎn)換.

上面的那個任務(wù),會先獲取讀鎖,并嘗試輸出count變量的值.但是當其等于0時,它會將讀鎖轉(zhuǎn)換成寫鎖,如果轉(zhuǎn)換成功,則將23賦給count.如果不成功,則直接通過** lock.writeLock()**來以阻塞的方式獲取寫鎖.然后將23賦給count.最后,再釋放鎖.

Semaphores

除了鎖,Concurrency API也支持信號量.鎖通常用于互斥的訪問某些資源,而信號量用于限制一定的線程可以同時訪問某些資源.

下面這個例子,演示了如何使用信號量:

Executor允許同時執(zhí)行十個線程,但是因為我們將信號量設(shè)置成了5.所以實際上,只有5個線程有機會來執(zhí)行操作.

輸出如下:

只有前五個線程,能夠獲得一個信號量,然后來執(zhí)行暫停五分鐘的操作.其他的線程,因為沒有信號量可以獲取了,就只能在控制臺打印處** Could not acquire semaphore**.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末共虑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拥坛,更是在濱河造成了極大的恐慌尘分,老刑警劉巖猜惋,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件著摔,死亡現(xiàn)場離奇詭異定续,居然都是意外死亡禾锤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門恩掷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來供嚎,“玉大人,你說我怎么就攤上這事寸宏。” “怎么了氮凝?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵望忆,是天一觀的道長。 經(jīng)常有香客問我稿壁,道長,這世上最難降的妖魔是什么傅是? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任蕾羊,我火速辦了婚禮,結(jié)果婚禮上龟再,老公的妹妹穿的比我還像新娘。我一直安慰自己浆劲,他們只是感情好哀澈,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著割按,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丙躏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天晒旅,我揣著相機與錄音,去河邊找鬼谈秫。 笑死鱼鼓,一個胖子當著我的面吹牛拟烫,可吹牛的內(nèi)容都是我干的迄本。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼置媳,長吁一口氣:“原來是場噩夢啊……” “哼公条!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起靶橱,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤关霸,失蹤者是張志新(化名)和其女友劉穎传黄,沒想到半個月后谒拴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涉波,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年苍日,在試婚紗的時候發(fā)現(xiàn)自己被綠了窗声。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡拦耐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杀糯,到底是詐尸還是另有隱情,我是刑警寧澤固翰,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站疗琉,受9級特大地震影響歉铝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜犯戏,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望种吸。 院中可真熱鬧,春花似錦坚俗、人聲如沸岸裙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剧董,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尉剩,已是汗流浹背毅臊。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朗鸠。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓式撼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親著隆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.解決信號量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 871評論 0 1
  • Java8張圖 11弦赖、字符串不變性 12浦辨、equals()方法、hashCode()方法的區(qū)別 13流酬、...
    Miley_MOJIE閱讀 3,693評論 0 11
  • 一、前言 借用Java并發(fā)編程實踐中的話"編寫正確的程序并不容易旦装,而編寫正常的并發(fā)程序就更難了"摊滔,相比于順序執(zhí)行的...
    運維開發(fā)筆記閱讀 329評論 0 2
  • 我是一個小孩子,盼望著爺爺家酸杏兒的傻孩子艰躺。 爺爺在冬天種了棵杏兒苗個頭還沒我高,我嘲笑杏兒真矮腺兴,可是爺爺卻摸著我...
    丁丁小午閱讀 339評論 0 0
  • 命令: 不要忘了把 .git 文件刪掉拘泞,重新指定 github 倉庫地址
    LeonardLT閱讀 512評論 2 0