讓人頭大的各類鎖,從這裏讓你思緒清晰

此次咱們來看鎖

說到了鎖咱們常常會聯想到生活中的鎖,在咱們平常中咱們常常會接觸到鎖。好比咱們的手機鎖,電腦鎖,再好比咱們生活中的門鎖,這些都是鎖。java

鎖有什麼做用呢?

說了這麼多仍是不清楚鎖到底有什麼用處?這一點就要深思咱們爲何要使用鎖,咱們用手機鎖是爲了保障咱們的隱私安全,使用門鎖是爲了保障咱們的財產安全,準確的來講咱們使用鎖就是爲了安全。
那麼在生活中咱們能夠加鎖來保障本身的隱私和財產安全,那Java中的鎖有什麼用處呢?算法

Java中的鎖

Java中的鎖準確的來講也是爲了保證安全,不過不一樣的是Java中的鎖是爲了保證併發所須要的。因此在Java中加鎖準確的來講是爲了保證併發安全,同時也是爲了解決內存中的一致性,原子性,有序性三種問題。在Java中提供了各式各樣的鎖,每種鎖都有其自身的特色和適用範圍。因此咱們都要熟悉鎖的區別和原理才能正確的使用。數據庫

樂觀鎖和悲觀鎖

悲觀鎖

樂觀鎖和悲觀鎖的話在以前我剛剛開始寫的時候就寫過相關的文章,在這裏就從新介紹一下吧。
悲觀鎖如其名它是悲觀的,它以爲每次訪問數據均可能被其餘人(線程)修改,因此在訪問資源的時候就會對資源進行加鎖,用這種方式來保證資源在訪問的時候不會被其餘線程修改。這樣的話其餘線程想要獲取資源的話就只能阻塞,等到當前線程釋放鎖後在獲取。在Java中悲觀鎖的實現有synchronized關鍵字Lock的實現類都是悲觀鎖。咱們來看一下悲觀鎖究竟是怎麼執行的。

線程A搶佔到資源後線程B就陷入了阻塞中,而後就等待線程A釋放資源。
安全

當線程A釋放完資源後線程B就去獲取鎖開始操做資源˛悲觀鎖保證了資源同時只能一個線程進行操做。多線程

樂觀鎖

與悲觀鎖相反,樂觀鎖並不會以爲訪問數據的時候會有人修改(因此它是樂觀的),因此在訪問資源的時候並不會上鎖,可是在提交的時候回去判斷一下是否有人修改了當前數據,在數據庫中咱們可使用version版本號去實現。在Java中咱們是使用CSA來實現。咱們看一下樂觀鎖的執行過程
併發

CAS

CAS(Compare And Swap)算法是一種無鎖算法,是Java提供的非阻塞原子性操做。在不使用鎖的狀況下實現多線程下的同步。在併發包中(java.util.concurrent)原子性類都是使用CAS來實現樂觀鎖的。CAS經過硬件保證了比較更新的原子性,在JDK中Unsafe提供了一系列的compareAndSwap*方法,這裏就不深究Unsafe這個類了。
CAS操做過程就是將內存中的將要被修改的數據與預期的值進行比較,若是這兩個值相等就修改值爲新值,不然就不作操做也就是說CAS須要三個操做值:性能

  • 預期值的 A
  • 內存中的V
  • 將要修改的B
簡單的來講CAS就是一個死循環,在循環中判斷預期的值和內存中的值是否相等,若是相等的話就執行修改,若是若是不相等的話就繼續循環,直到執行成功後退出。

CAS的問題

CAS雖然很牛逼可是它也存在一些問題好比ABA問題,舉個例子,如今有內存中有一個共享變量X的值爲A,這個時候出現一個變量想要去修改變量X的值,首先會獲取X的值這個時候獲取的是A,而後使用CAS操做把X變量修改爲B。這樣看起來是沒有問題,那若是在線程1獲取變量X以後,執行CAS以前出現一個線程2把X的值修改爲B而後CAS操做執行又修改爲了了A,雖然最後執行的結果共享變量的值爲A可是此A已經不是線程1獲取的A了。
這就是經典的ABA問題。產生ABA問題是由於變量的狀態值發生了環形轉換,A能夠到B,B能夠到A,若是A到B,B到C就不會發生這種問題。spa

解決辦法:在JDK1.5後加入了AtomicStampedReference方法給每一個變量加入了一個時間戳來避免ABA問題。
同時CAS還有循環開銷大的問題,由於會一直循環直到預期和內存相等修改爲功。同時還有隻能保證一個共享變量的原子性的問題不過在JDK1.5以後加入了AtomicReference類來保證引用對象之間的原子性。

使用悲觀鎖和樂觀鎖

可使用synchronized關鍵字來實現悲觀鎖,樂觀鎖可使用並法包下提供的原子類。線程

公平鎖和非公平鎖

上面說了悲觀鎖和樂觀鎖,如今來看公平鎖和非公平鎖。在鎖中也是有公平和不公平滴,公平鎖如其名講究的是一個公平,因此多個線程同時申請申請鎖的話,線程會放入一個隊列中,在隊列中第一個進入隊列的線程才能獲取鎖資源,講究的是先到先得。就好比咱們在學校食堂打飯的時候,那個時候記得我同窗一放學就趕快去食堂排隊這樣的話才能儘快的打上飯,並且在排隊的過程當中並不會有人吃不到飯,這個時候食堂阿姨是公平的每一個人排隊的話都能吃到飯,線程也是如此。非公平鎖能夠這樣理解,我那個同窗去食堂排隊打飯了可是有人卻插隊,食堂阿姨卻不公平直接給插隊的人打飯卻不給他打,你說氣不氣是否是很不公平,劃重點非公平鎖先到不必定先得。不過公平鎖也是有缺點的,當一個線程獲取資源後在隊列中的其餘的線程就只能在阻塞,CPU的因此公平鎖比非公平鎖的效率要低不少。由於CPU喚醒阻塞線程的開銷比非公平鎖大。咱們來看一個一個例子:
3d

在Java中ReentrantLock提供了公平鎖和非公平鎖的實現。看一下ReentrantLock怎麼實現公平鎖和非公平鎖

使用公平鎖和非公平鎖

ReentrantLock默認就是非公平的鎖,咱們來看一下公平鎖的例子:

看一下輸出結果:

咱們能夠看到公平鎖的輸出結果是按照順序來的,先到先得。
在看一下非公平鎖的例子:

輸出結果:

咱們能夠看到若是使用非公平鎖的話最後輸出的結果是徹底沒有順序的,先到不必定先得。
因此在使用公平鎖的時候線程1獲取到鎖以後線程2在請求鎖的話就會掛起等待線程1釋放鎖,而後線程2才能獲取鎖。若是再有一個線程3想要請求鎖的話,這時候若是使用的是非公平鎖,那麼線程2和線程3中兩個有一個會獲取到鎖,公平鎖的狀況下線程3只能先掛起,等待線程2獲取鎖資源釋放後在獲取。

何時使用公平鎖和非公平鎖

在須要公平資源的場景下使用公平鎖,若是不須要特殊的公平對待的話儘可能使用非公平鎖,由於公平鎖會帶來性能的開銷。

獨佔鎖和共享鎖

看到獨佔和共享會聯想到什麼,對的獨佔鎖就是每次只有一個線程能霸佔這個鎖資源,而其餘線程就只能等待當前獲取鎖資源的線程釋放鎖才能再次獲取鎖,剛剛上面的ReentrantLock就是獨佔鎖,那這樣看來獨佔鎖不也就是悲觀鎖嗎?由於悲觀鎖搶佔資源後就只能等待釋放其餘線程才能再次獲取到鎖資源。其實準確的說獨佔鎖也是悲觀鎖。
在談共享鎖,共享鎖其實也是樂觀鎖它放寬了鎖的策略容許多個線程同時獲取鎖。在併發包中ReadWriteLock就是一個典型的共享鎖。它容許一個資源能夠被多個讀操做訪問,或者被一個 寫操做訪問,但二者不能同時進行。

自旋鎖

什麼是自旋鎖,自旋鎖其實就是當一個線程獲取鎖的時候,這個鎖已經被其餘人獲取到了那麼這個線程不會立馬掛起,反而在不放棄CPU使用權的狀況下會嘗試再次獲取鎖資源,默認次數是10次,可使用-XX: PreBlockSpinsh來設置次數。若是自旋鎖獲取鎖的時間太長,會形成後面的線程CPU資源耗盡釋放。而且自旋鎖是不公平的。

優勢

自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減小了沒必要要的上下文切換,執行速度快。

生活中有各類意想不到的情況,Java中也有各類意想不到的異常,下次咱們聊聊Java中的異常,歡迎轉發關注

相關文章
相關標籤/搜索