您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中鎖的實現原理和實例用法”,在日常操作中,相信很多人在Java中鎖的實現原理和實例用法問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中鎖的實現原理和實例用法”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一、為什么要用鎖?
鎖-是為了解決并發操作引起的臟讀、數據不一致的問題。
二、鎖實現的基本原理
2.1、volatile
Java編程語言允許線程訪問共享變量, 為了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖要更加方便。
volatile在多處理器開發中保證了共享變量的“ 可見性”。可見性的意思是當一個線程修改一個共享變量時,另外一個線程能讀到這個修改的值。
結論:如果volatile變量修飾符使用恰當的話,它比synchronized的使用和執行成本更低,因為它不會引起線程上下文的切換和調度。
2.2、synchronized
synchronized通過鎖機制實現同步。
先來看下利用synchronized實現同步的基礎:Java中的每一個對象都可以作為鎖。
具體表現為以下3種形式。
對于普通同步方法,鎖是當前實例對象。
對于靜態同步方法,鎖是當前類的Class對象。
對于同步方法塊,鎖是Synchonized括號里配置的對象。
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
2.2.1 synchronized實現原理
synchronized是基于Monitor來實現同步的。
Monitor從兩個方面來支持線程之間的同步:
互斥執行
協作
1、Java 使用對象鎖 ( 使用 synchronized 獲得對象鎖 ) 保證工作在共享的數據集上的線程互斥執行。
2、使用 notify/notifyAll/wait 方法來協同不同線程之間的工作。
3、Class和Object都關聯了一個Monitor。
Monitor 的工作機理
線程進入同步方法中。
為了繼續執行臨界區代碼,線程必須獲取 Monitor 鎖。如果獲取鎖成功,將成為該監視者對象的擁有者。任一時刻內,監視者對象只屬于一個活動線程(The Owner)
擁有監視者對象的線程可以調用 wait() 進入等待集合(Wait Set),同時釋放監視鎖,進入等待狀態。
其他線程調用 notify() / notifyAll() 接口喚醒等待集合中的線程,這些等待的線程需要重新獲取監視鎖后才能執行 wait() 之后的代碼。
同步方法執行完畢了,線程退出臨界區,并釋放監視鎖。
參考文檔:https://www.ibm.com/developerworks/cn/java/j-lo-synchronized
2.2.2 synchronized具體實現
1、同步代碼塊采用monitorenter、monitorexit指令顯式的實現。
2、同步方法則使用ACC_SYNCHRONIZED標記符隱式的實現。
通過實例來看看具體實現:
javap編譯后的字節碼如下:
monitorenter
每一個對象都有一個monitor,一個monitor只能被一個線程擁有。當一個線程執行到monitorenter指令時會嘗試獲取相應對象的monitor,獲取規則如下:
如果monitor的進入數為0,則該線程可以進入monitor,并將monitor進入數設置為1,該線程即為monitor的擁有者。
如果當前線程已經擁有該monitor,只是重新進入,則進入monitor的進入數加1,所以synchronized關鍵字實現的鎖是可重入的鎖。
如果monitor已被其他線程擁有,則當前線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試獲取monitor。
monitorexit
只有擁有相應對象的monitor的線程才能執行monitorexit指令。每執行一次該指令monitor進入數減1,當進入數為0時當前線程釋放monitor,此時其他阻塞的線程將可以嘗試獲取該monitor。
2.2.3 鎖存放的位置
鎖標記存放在Java對象頭的Mark Word中。
Java對象頭長度
32位JVM Mark Word 結構
32位JVM Mark Word 狀態變化
64位JVM Mark Word 結構
2.2.3 synchronized的鎖優化
JavaSE1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”。
在JavaSE1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。
鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。
偏向鎖:
無鎖競爭的情況下為了減少鎖競爭的資源開銷,引入偏向鎖。
輕量級鎖:
輕量級鎖所適應的場景是線程交替執行同步塊的情況。
鎖粗化(Lock Coarsening):也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續的鎖擴展成一個范圍更大的鎖。
鎖消除(Lock Elimination):鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除。
適應性自旋(Adaptive Spinning):自適應意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間,比如100個循環。另一方面,如果對于某個鎖,自旋很少成功獲得過,那在以后要獲取這個鎖時將可能省略掉自旋過程,以避免浪費處理器資源。
2.2.4 鎖的優缺點對比
2.3、CAS
CAS,在Java并發應用中通常指CompareAndSwap或CompareAndSet,即比較并交換。
1、CAS是一個原子操作,它比較一個內存位置的值并且只有相等時修改這個內存位置的值為新的值,保證了新的值總是基于最新的信息計算的,如果有其他線程在這期間修改了這個值則CAS失敗。CAS返回是否成功或者內存位置原來的值用于判斷是否CAS成功。
2、JVM中的CAS操作是利用了處理器提供的CMPXCHG指令實現的。
優點:
競爭不大的時候系統開銷小。
缺點:
循環時間長開銷大。
ABA問題。
只能保證一個共享變量的原子操作。
三、Java中的鎖實現
3.1、隊列同步器(AQS)
隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步組件的基礎框架。
3.1.1、它使用了一個int成員變量表示同步狀態。
3.1.2、通過內置的FIFO雙向隊列來完成獲取鎖線程的排隊工作。
同步器包含兩個節點類型的應用,一個指向頭節點,一個指向尾節點,未獲取到鎖的線程會創建節點線程安全(compareAndSetTail)的加入隊列尾部。同步隊列遵循FIFO,首節點是獲取同步狀態成功的節點。
未獲取到鎖的線程將創建一個節點,設置到尾節點。如下圖所示:
首節點的線程在釋放鎖時,將會喚醒后繼節點。而后繼節點將會在獲取鎖成功時將自己設置為首節點。如下圖所示:
3.1.3、獨占式/共享式鎖獲取
獨占式:有且只有一個線程能獲取到鎖,如:ReentrantLock;
共享式:可以多個線程同時獲取到鎖,如:CountDownLatch;
獨占式
每個節點自旋觀察自己的前一節點是不是Header節點,如果是,就去嘗試獲取鎖。
獨占式鎖獲取流程:
共享式:
共享式與獨占式的區別:
共享鎖獲取流程:
四、鎖的使用用例
4.1、ConcurrentHashMap的實現原理及使用
ConcurrentHashMap類圖
ConcurrentHashMap數據結構
結論:ConcurrentHashMap使用的鎖分段技術。首先將數據分成一段一段地存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。
到此,關于“Java中鎖的實現原理和實例用法”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。