java 锁_Java之线程并发的各种锁、锁、锁
因為兩周沒更新了...
也不是懶,這兩周確實有些忙,趕項目進度趕的不亦樂乎...
終于趕在工期前,可以進入內測了,我也有了些時間,可以更新啦...
????線程并發鎖是很常見的問題,而且在Java中鎖的類型、概念、使用場景等等也是面試必問的,所以今天就來先簡單的說一說線程并發中常用的一下鎖。
0 1
? 公平鎖
????何為公平鎖?就字面來理解,它就是一種有公平機制,并且不會因為你有任何的“背景”、“關系”就可以為所欲為的鎖。在并發環境下,每個線程在獲取鎖時先會看該鎖維護的等待隊列,如果為空,或者當前線程時等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,此后所有的線程都是以FIFO(先進先出)的規則來執行。
????在Java中提供了公平鎖的實現類,先看個代碼demo:
打印值:
可以看到線程獲取到鎖一定和啟動保持一致的,另外ReentrantLock有參構造默認是true,也就是開啟公平鎖,
還有一點,代碼中有虛線的問題,是因為我創建線程是顯式的,alibaba檢測編碼規約是提示,必須使用線程池的方式創建線程,避免出現OOM。
優點:
????那公平鎖的有點也就一目了然了:所有的線程在CPU執行調度的時候,都能夠得到資源,不至于有些線程因為搶占不到資源,而一直無法被執行。
缺點:
????有優點就必然有缺點:既然可以有序的執行,那肯定就會降低吞吐量,隊列里面除了第一個線程,其它線程將會全部阻塞,CPU喚醒阻塞線程的開銷會很大。
0 2
非公平鎖
????何為非公平鎖?同樣字面意思理解,就是一種不公平機制的鎖,隨機搶占資源,不跟你整那一套先到先得的規則。線程加鎖時先嘗試獲取鎖,獲取不到就自動到隊尾等待。
上述同樣的代碼,把ReentrantLock的有參構造改成false,執行看下打印值:
非公平鎖時隨機搶占的機制,所以也會出現公平鎖的情況,所以測試時候不用糾結這個。
優點:
????非公平的性能肯定是高于公平鎖性能的,非公平鎖能更充分的利用CPU的時間片,盡量的減少CPU空閑的狀態時間。
缺點:
????既然是隨機搶占資源的,那就肯定會有隊列中間的線程一直獲取不到鎖或長時間獲取不到鎖,導致“餓死”。
0 3
可重入鎖
? ?????? ?何為可重入鎖:可重復可遞歸調用的鎖,在外層使用鎖之后,在內層仍然可以使用,并且不發生死鎖,但前提是同一個對象或同一個類中,這樣的鎖就叫可重入鎖。
????上面兩種鎖介紹的都是ReentrantLock實現的,而在真正的使用中保證線程安全synchronized也是不可缺少的,這兩者都是可重入鎖。
父方法和子方法同時輸出了線程的名稱,表明即使遞歸使用synchronized也沒有發生死鎖,證明其是可重入的。
那這兩者有什么區別呢?
0 4
ReentrantLock和synchronized區別
1,synchronized依賴于JVM,ReentrantLock依賴于API,需要lock()和unlock()方法配合try/finally語句塊來實現。
2,synchronized使用比較簡單方便,并且有編譯器去保證鎖的加鎖和釋放,而ReentrantLock需要手動聲明加鎖和釋放鎖,因此ReentrantLock鎖的粒度和靈活度要高于synchronized。
3,synchronized在優化以前,性能方面和ReentrantLock是差很多的,但是在JDK1.6之后synchronized引入了偏向鎖和自旋鎖,兩者的性能就差不多了,官方甚至都建議使用synchronized。其實synchronized的優化感覺就是采用ReentrantLock的CAS技術(這里不要有歧義:ReentrantLock底層是基于AQS實現的,但AQS的本質其實就是volatile+CAS)。都是試圖在用戶態就把加鎖的問題解決,避免進入內核態的線程阻塞。
0 5
讀寫鎖
????讀寫鎖實際上就一種特殊的自旋鎖,讀寫鎖的規則可以共享讀,但只能一個寫,讀讀不互斥、讀寫互斥、寫寫互斥,而一般的獨占鎖是啥都互斥,而我們實際場景中,讀一定是大于寫的,一般情況下獨占鎖的效率低來源于高并發下對臨界區的資源搶占導致線程上下文切換。因此當并發不是很高的情況下,讀寫鎖需要額外的維護讀鎖的狀態,可能還不如獨占鎖的效率高,所以要根據實際場景而定。(本篇不深入將方法的實現和源碼)
0 6
synchronized JDK1.6優化之后的鎖
6.1??偏向鎖
????????當線程執行到臨界區時,會利用CAS操作,將線程ID插入到Mark Word(對象頭中的標記子),同時修改偏向鎖的標志位。
????所謂臨界區,就是只允許一個線程進去執行操作的區域,即同步代碼塊,cas是一個原子性的操作。
????這個鎖會偏向于第一個獲得它的線程,在接下來的執行過程中,假如該鎖沒有被其它線程所獲取,沒有其它線程來競爭該鎖,那么持有偏向鎖的線程將永遠不需要進行同步操作。也就是說,在此線程之后的執行過程中,如果再次進入或者退出同一段同步塊代碼,并不再需要去進行加鎖或者解鎖操作。
6.2?鎖膨脹
????綜合偏向鎖,當出現有兩個線程來競爭的話,那么偏向鎖就失效了,此時鎖就會膨脹,升級為輕量鎖,這就是我們經常說的鎖膨脹。
6.3?鎖撤銷
????當偏向鎖失效,就得把該鎖撤銷,鎖撤銷的開銷花費是很大的,大概的過程:
在一個安全點停止擁有鎖的線程。
遍歷線程棧,如果存在鎖記錄的話,需要修復鎖記錄和Markword,使其變成無鎖狀態。
喚醒當前線程,將當前鎖升級成輕量級鎖。所以,如果某些同步代碼塊大多數情況下都是有兩個及以上的線程競爭的話,那么偏向鎖就會是一種累贅,對于這種情況,我們可以一開始就把偏向鎖這個默認功能給關閉
6.4?自旋鎖(jdk1.4.2引入的)
????自旋鎖屬于輕量級鎖。所謂自旋,就是指當有另外一個線程來競爭鎖時,這個線程會在原地循環等待,而不是把該線程阻塞,直到那個獲得鎖線程釋放鎖之后,這個線程就可以馬上獲取到鎖。
????注意:鎖在原地循環的時候,是會消耗CPU的,就相當于在循環一個啥也沒有的for循環。所以,輕量級鎖適用于那些同步代碼塊執行的很快的場景,這樣,線程原地等待很短很短的時間就能夠獲得鎖了。
經驗表明,大部分同步代碼塊執行的時間都是很短很短的,也正是基于這個原因,才有了輕量級鎖這么個東西。
自旋鎖存在的問題:
1,如果同步代碼塊執行的很慢,需要消耗大量的時間,這個時候,其它線程在原地等待空消耗的CPU,這點就會有問題。
2,本來一個線程把鎖釋放之后,當前線程是能夠獲得鎖的,但是假如這個時候好幾個線程都在競爭這個鎖的話,那么可能當前線程會獲取不到鎖,還在原地”轉圈圈“,轉到懷疑人生。
那基于這個問題,我們就需要給線程空循環設置一個值,當線程超過了這個次數,我們就認為繼續使用自旋鎖就不合適了,一直原地空循環,這誰受得了。此時鎖就會再次膨脹,升級為重量級鎖。
默認情況下,自旋的次數為10次,當然可以通過-XX:PreBlockSpin來進行更改。
6.5?自適應自旋鎖
????所謂自適應自旋鎖就是線程空循環等待的自旋次數并非是固定的,而是會動態著根據實際情況來改變自旋等待的次數。
其大概原理是這樣的:
????假如一個線程1剛剛成功獲得一個鎖,當它把鎖釋放了之后,線程2獲得該鎖,并且線程2在運行的過程中,此時線程1又想來獲得該鎖了,但線程2還沒有釋放該鎖,所以線程1只能自旋等待,但是虛擬機認為,由于線程1剛剛獲得過該鎖,那么虛擬機覺得線程1這次自旋也是很有可能能夠再次成功獲得該鎖的,所以會延長線程1自旋的次數。
另外,如果對于某一個鎖,一個線程自旋之后,很少成功獲得該鎖,那么以后這個線程要獲取該鎖時,是有可能直接忽略掉自旋過程,直接升級為重量級鎖的,以免空循環等待浪費資源。
輕量級鎖也被稱為非阻塞同步、樂觀鎖,因為這個過程并沒有把線程阻塞掛起,而是讓線程空循環等待,串行執行。
本篇著重講的是一下名詞的概念,之后會對于ReentrantLock的源碼解析來真正的理解這些鎖的底層實現原理,因為知其然先知其所以然啦。
- End -
總結
以上是生活随笔為你收集整理的java 锁_Java之线程并发的各种锁、锁、锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 巷字电脑五笔怎么打(巷五笔怎么打出来)
- 下一篇: java iterator获取索引_20