1、爲何要用鎖?java
鎖-是爲了解決併發操做引發的髒讀、數據不一致的問題。編程
2、鎖實現的基本原理併發
2.1、volatile編程語言
Java編程語言容許線程訪問共享變量, 爲了確保共享變量能被準確和一致地更新,線程應該確保經過排他鎖單獨得到這個變量。Java語言提供了volatile,在某些狀況下比鎖要更加方便。性能
volatile在多處理器開發中保證了共享變量的「 可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。優化
結論:若是volatile變量修飾符使用恰當的話,它比synchronized的使用和執行成本更低,由於它不會引發線程上下文的切換和調度。this
2.2、synchronizedspa
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標記符隱式的實現。
經過實例來看看具體實現:
public class SynchronizedTest {
public synchronized void method1(){
System.out.println("Hello World!");
}
public void method2(){
synchronized (this){
System.out.println("Hello World!");
}
}
}
2.2.3 synchronized的鎖優化
JavaSE1.6爲了減小得到鎖和釋放鎖帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」。
在JavaSE1.6中,鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭狀況逐漸升級。
鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率。
偏向鎖:
無鎖競爭的狀況下爲了減小鎖競爭的資源開銷,引入偏向鎖。
輕量級鎖:
輕量級鎖所適應的場景是線程交替執行同步塊的狀況。
鎖粗化(Lock Coarsening):也就是減小沒必要要的緊連在一塊兒的unlock,lock操做,將多個連續的鎖擴展成一個範圍更大的鎖。
鎖消除(Lock Elimination):鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行削除。
適應性自旋(Adaptive Spinning):自適應意味着自旋的時間再也不固定了,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。若是在同一個鎖對象上,自旋等待剛剛成功得到過鎖,而且持有鎖的線程正在運行中,那麼虛擬機就會認爲此次自旋也頗有可能再次成功,進而它將容許自旋等待持續相對更長的時間,好比100個循環。另外一方面,若是對於某個鎖,自旋不多成功得到過,那在之後要獲取這個鎖時將可能省略掉自旋過程,以免浪費處理器資源。