Java對象鎖


對象鎖(monitor)
  機制是JDK 1.6 之前synchronized底層原理,又稱為JDK 1.6重量級鎖,
  線程的阻塞以及喚醒均需要由用戶態切換到內核態,開銷非常大,因此效率很低。
 
   Lock鎖 - JDK 1.5(juc) - java語言層鎖
  JDK 1.6之后對於內建鎖的優化
 
1.CAS(Compare and Swap)
  • 悲觀鎖:線程獲取鎖(JDK 1.6之前內建鎖)是一種悲觀鎖策略。假設每一次執行臨屆區代碼(訪問共享資源)都會產生沖突,所以當前線程獲取1鎖的同時也會阻塞其他未獲取到鎖的進程。
  • 樂觀鎖(CAS操作,無鎖操作):假設所有線程訪問共享資源時不會出現沖突,由於不會出現沖突自然就不會阻塞其他線程。因此線程就不會出現阻塞停頓狀態。出現沖突時,無鎖操作使用CAS(比較交換)來鑒別線程是否出現沖突,出現沖突就重試當前操作直到沒有沖突為止。
 
1.1 CAS操作過程:
  CAS 可以理解為CAS(V,O,N): V:當前內存地址實際存放值   O:預期值(舊值)    N:更新的新值
 
  當V=O時,期望值與內存實際值相等,該值沒有被任何其他線程修改過,即值O就是目前的最新值,因此可以將新值N賦值給V。
      反之,如果V!=O,表明該值已經被其他線程修改過了,因此O值並不是當前最新值,返回V,無法修改。
 
  當多個線程使用CAS操作時,只有一個線程會成功,其余線程均失敗。失敗的線程會重新嘗試(自旋)或掛起線程(阻塞)。
 
  內建鎖在老版本最大的問題在於:在存在線程競爭的情況下會出現線程的阻塞以及喚醒帶來的性能問題,這是一種互斥同步(阻塞同步)。
  而CAS不是將線程掛起,當CAS失敗后會進行一定的嘗試操作並耗時將線程掛起,也叫做非阻塞同步。
 
1.2 CAS 的問題
 
   1.2.1 ABA問題
    解決:使用atomic ATomicStampedReference類來解決
    添加版本號   1A -> 2B -> 3A
 
   1.2.2 自旋會浪費大量CPU資源。
    與線程阻塞相比,自旋會浪費大量的處理器資源。因為當前線程仍處於運行狀態,只不過跑的是無用指令。
    解決:自適應自旋(重量級鎖的優化):根據以往自旋等待時能否獲取鎖,來動態調整自旋時間(循環次數)。如果自旋時獲取到鎖,則會稍微增加自旋時長;否則就會稍微減少下一次自旋時長。
 
   1.2.3 公平性
    內建鎖無法實現公平機制,而Lock體系可以實現公平鎖。
 
2.java對象頭
 
  java對象頭Mark Word 字段存放內容
  對象的Hashcode
  分代年齡
  鎖標記位
 
  JDK 1.6 之后一共有四種狀態的鎖:
  無鎖    0 01
  偏向鎖    1 01 
  輕量級鎖    00
  重量級鎖    10
  根據競爭狀態的激烈程度,鎖會自動進行升級,鎖不能降級(為了提高鎖獲取與釋放的效率)。
 
3.偏向鎖
  大多數情況下,鎖不僅不存在多線程競爭,而且總是由一個線程多次獲得。為了讓線程獲取鎖的開銷降低引入偏向鎖。
  偏向鎖是鎖狀態中最樂觀的一種鎖:從始至終只有一個線程請求一把鎖。
 
  偏向鎖的獲取:當一個線程訪問同步塊並成功取得鎖時,會在對象頭和棧幀中的鎖記錄字段存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS來加鎖和解鎖,直接進入
 
  當線程訪問同步塊失敗時,使用CAS競爭鎖,並將偏向鎖升級為輕量級鎖。
 
  偏向鎖頭部Epoch字段值:表示此對象偏向鎖的撤銷次數 默認撤銷40次以上,表示此對象不再適用於偏向鎖,當下一次再次獲取此對象鎖時,直接變為輕量級鎖。
 
  偏向鎖只有一次CAS過程,出現在第一次加鎖時。
 
  偏向鎖的撤銷:
  偏向鎖使用了一種等待競爭出現才釋放鎖的機制,所以當其他線程競爭偏向鎖時,持有偏向鎖的線程才會釋放偏向鎖,並將鎖膨脹為輕量級鎖(持有偏向鎖的線程依然存活的時候)。
 
  如果持有線程已經終止,則將鎖對象的對象頭設置成為無鎖狀態。
 
  JDK 6 之后偏向鎖默認開啟。
 
4.輕量級鎖
  多個線程在不同的時間段請求同一把鎖,也就是不存在鎖競爭的情況,針對這種情況,JVM采用輕量級鎖,來避免線程阻塞以及喚醒。
 
 
 
5,三種鎖特點
  a,偏向鎖只會在第一次請求鎖時采用CAS操作並將鎖對象的標記字段記錄下來當前線程地址。在此后的運行過程中,持有偏向鎖的線程必須加鎖操作。
針對的是鎖僅會被同一線程持有情況。
  b,輕量級鎖采用CAS操作,將所對象標記字段替換為一個指針,只想當前線程棧上的一塊空間,存儲着鎖對象原本的標記字段。
針對的是多個線程你在不同時間段申請同一把鎖情況
 
  c,重量級鎖會阻塞,喚醒請求加鎖線程,針對的是多個線程同時競爭同一把鎖的情況,JVM采用自適應自旋,來避免在面對非常小的同步代碼塊時,扔回被阻塞和喚醒 的情況。
6.其他優化
 
  鎖粗化
  鎖粗化就是將多次鏈接在一起的加鎖、解鎖操作合並為一次操作。將多個聯系的鎖擴展為一個范圍更大的鎖。
 
  鎖解除
  刪除不必要的加鎖操作。根據代碼逃逸技術,如果判斷一段代碼中,堆上的數據不會逃逸當前線程,則認為此代碼是線程安全的無需加鎖。
 
7.Java.util.lock
 
   7.1 lock簡介
 
 Lock lock = new ReentrantLock();
  try{
  lock.lock();
  //以下代碼只有一個線程可進行
  ...
  }
  finally{
      lock.unlock();
  }

 7.2 lock常用API

    lock 體系擁有可中斷的獲取鎖以及超時獲取鎖以及共享鎖等內建鎖不具備的特性。
    void lock();//獲取鎖
    void lockInterruptibly( )throws InterruptedException();//響應中斷鎖
    boolean tryLock();//獲取鎖返回true,反之返回false
    boolean tryLock(long time, TimeUnit unit);//超時獲取鎖,在規定時間內未獲取到鎖返回false
    Condition newCondition();//獲取與Lock綁定的等待通知組件
    void unlock();//釋放鎖
    ReentrantLock中所有的方法實際上都是調用了其靜態內部類Sync中的方法,而Sync繼承了AbstQueuedSynchronizer(AQS——簡稱同步器)
 
7.3 AQS——同步器
 
    同步器是用來構建鎖及其他的同步組件的基礎框架,它的實現主要依賴一個int狀態變量以及通過一個FIFO隊列共同構成的同步隊列。
    子類必須重寫AQS的用proteced修飾的用來改變同步狀態的方法,其他方法主要是實現了排隊與阻塞機制,int狀態的更新使用getState()、setStact()、以及compareAndSetState()。
    子類推薦使用靜態內部類來繼承AQS實現自己的同步語義。同步器及支持獨占鎖,也支持共享鎖。
7.4 AQS的模板模式
  AQS使用模板方法模式,將一些與狀態相關的核心方法開發給子類重寫,而后AQS會使用子類重寫的關於狀態的方法進行線程排隊、等待喚醒操作
 
   鎖與AQS關系
    鎖面對使用者,定義了使用者與鎖交互接口
    同步器面向鎖的實現者,簡化了鎖的實現方式屏蔽了同步狀態的管理、線程排隊、線程等待喚醒等底層操作。
 
7.5 AQS詳解
  在同步組件中,AQS是最核心的部分,同步組件的實現依賴AQS提供的模板方法來實現同步組件語義。
  AQS實現了對同步狀態管理,以及對阻塞線程進行排隊,等待通知等底層實現
 
  AQS核心組成:同步隊列,獨占鎖的獲取與釋放。共享鎖的獲取與釋放、可中斷鎖、超時鎖。這一系列功能的實現依賴AQS所實現的功能方法
 
 
獨占鎖:
  1.void acquire(int arg)
  獨占式獲取同步狀態,如果獲取失敗插入同步隊列進行等待。
  2.void acquireInteruptibly(int arg)
  在1的基礎上,此方法可以在同步隊列中響應中斷
  3.boolean tryAcquirNanos(int arg,long nanosTimeOut)
  在2的基礎上增加超時等待功能,到了預計時間還沒獲得鎖直接返回
  4.boolean tryAcquire(int arg)
  獲取鎖成功返回true 否則返回false
  5.boolean release (int arg)
  釋放同步狀態,該方法會喚醒在同步隊列的下一個結點
 
共享式鎖
  1.void acquireShared(int arg )
  共享獲取同步狀態,同一時刻多個線程獲取同步狀態
  2.void acquireShareInterruptibly(int arg)在1的基礎上增加響應中斷
  3.boolean tryAcquirNanos(int arg,long nanosTimeOut)
  在2的基礎上增加超時等待功能,
  4.boolean releaseShared (int arg)共享式釋放同步狀態
 
同步隊列
  在AQS內部有一個靜態內部類Node,這是同步對列每個具體的結點
  節點有如下屬性:
  int waitStatus:結點狀態
 
  Node prev :同步對列中的前驅結點
  Node next:
  Thread thread:當前結點包裝線程對象
  Node nextWaiter:等待隊列中下一個結點
 
 
結點狀態值如下:
  int INITIAL = 0;  初始狀態
  int CANCELLED =1;  當前結點從同步隊列中取消
  int SIGNAL = -1; 后繼結點處於等待狀態,如果當前結點釋放同步狀態后會通知后繼結點,使后繼結點繼續運行
   int CONITION = -2  結點處於等待隊列中。當其他線程對Condition調用signal()方法后,該節點從等待隊列進同步隊列
   int PROPAGATE = -3 共享式同步狀態會無條件傳播
   AQS 同步隊列采用帶頭結點的雙向鏈表
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM