synchronized與Lock 擂臺之戰

面試官:說說synchronized和Lock(或ReentrantLock)的區別

Java 1.5之後,對共享變量訪問的協調機制除了之前的synchronized和volatile又多了一個Lock,深刻理解synchronized與Lock,並熟悉兩者的應用場景對編寫併發程序有着非常重要的作用


部落新添大將

話說JDK1.5之前,併發部落 synchronized 和 volatile 可謂紅人,無人不知,無人不曉

當多個線程訪問同一共享變量的時候,只需在操作該共享變量的方法上加一個synchronized,就可以保證同一時刻只有一個線程操作該共享變量(還有其他用法)

這使得多個線程按照要求合理的佔用和釋放資源,所以併發部落如此昌盛,synchronized 功不可沒

JDK1.5的到來,打破了這種局面,一個名爲Lock的接口出世,聽說這個Lock剛出世就神通廣大,不僅有着和synchronized一樣的功能,在鎖的獲取上還可以定時獲取,輪詢獲取和可中斷獲取等等一系列高級的技能

這消息傳到了synchronized的耳中,心中很是不甘,決定找個擂臺與Lock一決勝負,可是想到Lock有那麼多的優勢,自己心中頓時沒了底氣,所以他決定對自己升級一下再去PK

synchronized找到了JDK老大訴苦,說要改造改造自己,JDK老大說道:“之前一直有人抱怨你慢,因爲線程要獲得鎖和釋放鎖都要進行一次重量級的系統調用,我也想着給你優化優化”

“好啊好啊,synchronized說道”,“其實我這裏已經有方案了,你暫且回去,等JDK1.6的到來吧”JDK說道, “好的”,synchronized回覆到。

新synchronized問世

終於JDK1.6到來了,synchronized在鎖的獲取和釋放上有了重大改進,引入了偏向鎖、輕量級鎖和重量級鎖,這次synchronized信心大增,決定去找Lock PK

synchronized 到了 Lock 跟前,說道:“久聞Lock兄神通廣大,今日一見,不知神通在何處?”

Lock 一看這傢伙是來挑事的,自己也不甘示弱,”神通之處你自然看不出來,用時方顯神通”,Lock迴應道

synchronized氣的咬牙切齒,但這也正和自己心意,“哦,那我想見識見識,明日部落有一場擂臺比武,不知Lock兄能否奪得桂冠”

“那是必然”,Lock迴應道

“那明日一決雌雄”,synchronized甩下一句就走了

擂臺比武

次日,兩人都來到了擂臺旁,併發部落的人幾乎都來了,都想看看synchronized和Lock的好戲

用法PK

synchronized說道:“首先我是一個關鍵字,我常常被人稱爲內置鎖,我的使用特別簡單,如果你想讓某一個方法在同一時刻只能由一個線程訪問,那麼只需要在方法上加上一個synchronized,如下:”

這裏寫圖片描述

“這樣對變量 i 的修改就線程安全了”,synchronized說道,“對了,更爲讓人清爽的是,我的加鎖和釋放鎖都是隱式的,不需要程序員們在代碼層次上手動的去加鎖和釋放鎖,是不是很優雅”

聽完synchronized的一番自述後,雖說在用法上稍遜synchronized,但是Lock也不甘示弱,說道:

“我是一個接口,可以有無數的子類去實現我,ReentrantLock就是一個,我的使用也很簡單呀,當你想給某一段代碼加鎖的話,只需要在之前調用lock()方法,在之後調用unlock()不信你看:”

這裏寫圖片描述

雖說加鎖和釋放鎖都要在代碼層次上顯示的去操作,稍複雜一些,不是很優雅,但是我有synchronized所不具備的高級功能,Lock拿出自己的優勢來彌補了一下自己的不足

性能PK

早有準備的synchronized暗自竊喜,在這方面現在自己不比Lock差,synchronized說道,“性能這塊我現在引入了偏向鎖,輕量級鎖和重量級鎖,這些改變我的性能比之前提高了許多”

“提高了許多現在才和我差不多”Lock 插了一刀,氣的synchronized無話可說

用途PK

synchronized這塊很是心虛,論用途,Lock比自己多,但是自己還是心理給自己打氣,說道:

“併發控制這塊的需求,基本上我都可以解決,而且用法很優雅,許多程序員已經習慣了我的存在,並且在發生異常的時候,JVM老大會自動釋放鎖,這樣就避免了死鎖的產生”

synchronized抓住Lock一定要調用unlock()方法釋放鎖的缺點不放

“不像有些人,如果不主動 調用 unlock() 釋放鎖,就很可能造成死鎖”,synchronized又補了一句

Lock立馬迴應道:“我雖有些許不足,但是我的高級功能很強大,synchronized可以實現的功能我都可以實現,除此之外,我還有 等待可中斷可實現公平鎖以及鎖可以綁定多個條件等高級功能”

臺下的觀衆眼中放光,特別想聽聽這些都是什麼東西,之前都沒見過

只見Lock清了清嗓門,一個一個的解釋起來了

① 所謂等待可中斷就是一個線程去獲得一個鎖的時候,由於很長時間沒有獲取到鎖,可以放棄等待,處理其他事情

比如有兩個線程 A 和 B,A獲得了鎖,B想獲得該鎖,那麼B線程就掛起了,如果A遲遲不肯放鎖,如果該鎖的獲取是可以響應中斷(調用lock.lockInterruptibly()),那麼當其他線程中斷該線程的時候,則B線程就可以響應中斷,去做其他事情了

//在獲取鎖時被中斷,拋出 InterruptedException
lock.lockInterruptibly()

如下圖:
這裏寫圖片描述

run() 方法是 B 線程(繼承了Thread)的方法,當主線程調用 B 線程的interrupt()方法時,就會進入到 catch 並執行下面的任務,不必要在read函數的獲得鎖上一直阻塞等待(B線程它一直想獲得A線程的所持有的鎖,但是遲遲沒有得到)

②可實現公平鎖:所謂公平鎖,就是在多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖

比如A先來獲得鎖,獲取失敗,阻塞等待,然後B來獲取鎖,獲取失敗,阻塞等待,最後當這個鎖釋放的時候,按照公平鎖的原則,A肯定會得到鎖

在ReentrantLock的構造函數中,可以傳入是否爲公平鎖的參數,如:

// 使用公平鎖機制
Lock lock = new ReentrantLock(true);

③ 所謂 鎖綁定多個條件是指,一個 ReentrantLock 對象可以綁定多個 Condition對象(通過 lock.newCondition 創建一個條件

比如現在有兩個方法methodA和methodB,有兩個線程阻塞在methodA上,有一個線程阻塞在methodB上,現在想單獨喚醒methodA上的線程 或者單獨喚醒阻塞在methodB上的線程,如果是wait()和notify或notifyAll() 機制,則需要兩個鎖

而ReentrantLock只需要一把鎖就可以完成,一把鎖可以new 多個Condition

這裏寫圖片描述

在調用 lock.lock()後 可以調用 Condition 的 await() 方法使線程處於等待狀態,釋放鎖,如下:

這裏寫圖片描述

當有兩個線程進入methodA時,一個獲得鎖(lock.lock())後又釋放鎖(conditionA.await()),進入等待狀態,另一個線程同樣也進入等待狀態,當其他線程調用conditionA.signalAll()[也可調用conditionA.signal()] 的時候,可以喚醒在conditionA上等待的所有線程:如下

這裏寫圖片描述

這樣,在conditionA上等待的線程就被全部喚醒了,這和notifyAll()的作用一樣,只是這裏的condition 可以由一個 lock 創建很多,同樣的也可以在methodB中使用 conditionB, 最後用conditionB.signalAll() 來喚醒在 conditionB 上等待的所有線程

如果是wait 和 notify 的話,就需要多個鎖了

聽完Lock的自述後,大家都讚不絕口,地下一片掌聲

最後的勝負

這時候裁判大人出場了,聽完兩位大俠的敘述,我看兩人各有優缺,synchronized 爲許多開發人員所熟悉,並且簡潔緊湊,許多現有的程序都已經使用了synchronized

而 Lock 有許多高級功能,在一些特定的場合能派上大用處

但是Lock的危險性很高,如果忘記在finally塊中調用 unlock,那麼就埋下了一顆定時炸彈,所以只有當synchronized 不能滿足需求時,纔可以使用ReentrantLock

現在我宣佈,synchronized 略勝一籌,但Lock也是很不錯的


這裏寫圖片描述

掃碼關注,精彩不斷

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