重量級鎖?自旋鎖?自适應自旋鎖?輕量級鎖?偏向鎖?悲觀鎖?樂觀鎖?執行一個方法咋這麼辛苦,到處都是鎖。
今天這篇文章,給大家普及下這些鎖究竟是啥,他們的由來,他們之間有啥關系,有啥差別。
重量級鎖
如果你學過多線程,那麼你肯定知道鎖這個東西,至于為什麼需要鎖,我就不給你普及了,就當做你是已經懂的了。
我們知道,我們要進入一個同步、線程安全的方法時,是需要先獲得這個方法的鎖的,退出這個方法時,則會釋放鎖。如果擷取不到這個鎖的話,意味着有别的線程在執行這個方法,這時我們就會馬上進入阻塞的狀态,等待那個持有鎖的線程釋放鎖,然後再把我們從阻塞的狀态喚醒,我們再去擷取這個方法的鎖。
這種擷取不到鎖就馬上進入阻塞狀态的鎖,我們稱之為重量級鎖。
自旋鎖
我們知道,線程從運作态進入阻塞态這個過程,是非常耗時的,因為不僅需要儲存線程此時的執行狀态,上下文等資料,還涉及到使用者态到核心态的轉換。當然,把線程從阻塞态喚醒也是一樣,也是非常消耗時間的。
剛才我說線程拿不到鎖,就會馬上進入阻塞狀态,然而現實是,它雖然這一刻拿不到鎖,可能在下 0.0001 秒,就有其他線程把這個鎖釋放了。如果它慢0.0001秒來拿這個鎖的話,可能就可以順利拿到了,不需要經曆阻塞/喚醒這個花時間的過程了。
然而重量級鎖就是這麼坑,它就是不肯等待一下,一拿不到就是要馬上進入阻塞狀态。為了解決這個問題,我們引入了另外一種願意等待一段時間的鎖 --- 自旋鎖。
自旋鎖就是,如果此時拿不到鎖,它不馬上進入阻塞狀态,而是等待一段時間,看看這段時間有沒其他人把這鎖給釋放了。怎麼等呢?這個就類似于線程在那裡做空循環,如果循環一定的次數還拿不到鎖,那麼它才會進入阻塞的狀态。
至于是循環等待幾次,這個是可以人為指定一個數字的。
自适應自旋鎖
上面我們說的自旋鎖,每個線程循環等待的次數都是一樣的,例如我設定為 100次的話,那麼線程在空循環 100 次之後還沒拿到鎖,就會進入阻塞狀态了。
而自适應自旋鎖就牛逼了,它不需要我們人為指定循環幾次,它自己本身會進行判斷要循環幾次,而且每個線程可能循環的次數也是不一樣的。而之是以這樣做,主要是我們覺得,如果一個線程在不久前拿到過這個鎖,或者它之前經常拿到過這個鎖,那麼我們認為它再次拿到鎖的幾率非常大,是以循環的次數會多一些。
而如果有些線程從來就沒有拿到過這個鎖,或者說,平時很少拿到,那麼我們認為,它再次拿到的機率是比較小的,是以我們就讓它循環的次數少一些。因為你在那裡做空循環是很消耗 CPU 的。
是以這種能夠根據線程最近獲得鎖的狀态來調整循環次數的自旋鎖,我們稱之為自适應自旋鎖。
輕量級鎖
上面我們介紹的三種鎖:重量級、自旋鎖和自适應自旋鎖,他們都有一個特點,就是進入一個方法的時候,就會加上鎖,退出一個方法的時候,也就釋放對應的鎖。
之是以要加鎖,是因為他們害怕自己在這個方法執行的時候,被别人偷偷進來了,是以隻能加鎖,防止其他線程進來。這就相當于,每次離開自己的房間,都要鎖上門,人回來了再把鎖解開。
這實在是太麻煩了,如果根本就沒有線程來和他們競争鎖,那他們不是白白上鎖了?要知道,加鎖這個過程是需要作業系統這個大佬來幫忙的,是很消耗時間的,。為了解決這種動不動就加鎖帶來的開銷,輕量級鎖出現了。
輕量級鎖認為,當你在方法裡面執行的時候,其實是很少剛好有人也來執行這個方法的,是以,當我們進入一個方法的時候根本就不用加鎖,我們隻需要做一個标記就可以了,也就是說,我們可以用一個變量來記錄此時該方法是否有人在執行。也就是說,如果這個方法沒人在執行,當我們進入這個方法的時候,采用CAS機制,把這個方法的狀态标記為已經有人在執行,退出這個方法時,在把這個狀态改為了沒有人在執行了。
之是以要用CAS機制來改變狀态,是因為我們對這個狀态的改變,不是一個原子性操作,是以需要CAS機制來保證操作的原子性。
顯然,比起加鎖操作,這個采用CAS來改變狀态的操作,花銷就小多了。
然而可能會說,沒人來競争的這種想法,那是你說的而已,那如果萬一有人來競争說呢?也就是說,當一個線程來執行一個方法的時候,方法裡面已經有人在執行了。
如果真的遇到了競争,我們就會認為輕量級鎖已經不适合了,我們就會把輕量級鎖更新為重量級鎖了。
是以輕量級鎖适合用在那種,很少出現多個線程競争一個鎖的情況,也就是說,适合那種多個線程總是錯開時間來擷取鎖的情況。
偏向鎖
偏向鎖就更加牛逼了,我們已經覺得輕量級鎖已經夠輕,然而偏向鎖更加省事,偏向鎖認為,你輕量級鎖每次進入一個方法都需要用CAS來改變狀态,退出也需要改變,多麻煩。
偏向鎖認為,其實對于一個方法,是很少有兩個線程來執行的,搞來搞去,其實也就一個線程在執行這個方法而已,相當于單線程的情況,居然是單線程,那就沒必要加鎖了。
不過畢竟實際情況的多線程,單線程隻是自己認為的而已了,是以呢,偏向鎖進入一個方法的時候是這樣處理的:如果這個方法沒有人進來過,那麼一個線程首次進入這個方法的時候,會采用CAS機制,把這個方法标記為有人在執行了,和輕量級鎖加鎖有點類似,并且也會把該線程的 ID 也記錄進去,相當于記錄了哪個線程在執行。
然後,但這個線程退出這個方法的時候,它不會改變這個方法的狀态,而是直接退出來,懶的去改,因為它認為除了自己這個線程之外,其他線程并不會來執行這個方法。
然後當這個線程想要再次進入這個方法的時候,會判斷一下這個方法的狀态,如果這個方法已經被标記為有人在執行了,并且線程的ID是自己,那麼它就直接進入這個方法執行,啥也不用做
你看,多友善,第一次進入需要CAS機制來設定,以後進出就啥也不用幹了,直接進入退出。
然而,現實總是殘酷的,畢竟實際情況還是多線程,是以萬一有其他線程來進入這個方法呢?如果真的出現這種情況,其他線程一看這個方法的ID不是自己,這個時候說明,至少有兩個線程要來執行這個方法論,這意味着偏向鎖已經不适用了,這個時候就會從偏向鎖更新為輕量級鎖。
是以呢,偏向鎖适用于那種,始終隻有一個線程在執行一個方法的情況哦。
這裡我作下說明,為了友善大家了解,我在将輕量級鎖和偏向鎖的時候,其實是簡化了很多的,不然的話會涉及到對象的内部結構、布局,我覺得把那些扯出來,你們可能要暈了,是以我大緻講了他們的原理。
悲觀鎖和樂觀鎖
最開始我們說的三種鎖,重量級鎖、自旋鎖和自适應自旋鎖,進入方法之前,就一定要先加一個鎖,這種我們為稱之為悲觀鎖。悲觀鎖總認為,如果不事先加鎖的話,就會出事,這種想法确實悲觀了點,這估計就是悲觀鎖的來源了。
而樂觀鎖卻相反,認為不加鎖也沒事,我們可以先不加鎖,如果出現了沖突,我們在想辦法解決,例如 CAS 機制,上面說的輕量級鎖,就是樂觀鎖的。不會馬上加鎖,而是等待真的出現了沖突,在想辦法解決。
總結
到這裡也大緻寫完了,簡單介紹普及了一下,重點的大家要了解他們的由來,原理。每一種鎖都有他們的應用以及各自的優缺點,如果有機會,我再給大家說說他們各自的應用場景,優缺點,這個面試的時候,好像也會被經常到,今天先寫到這裡。
大家可以說說這些鎖的優缺點哦,例如與重量級鎖相比,自旋鎖容量導緻什麼問題的發生?悲觀鎖和樂觀鎖的比較呢?大家也可以評論區說說勒,這些是一定要搞懂的哦。