鎖機制用來保護對象的一致性以及操做的原子性,是實現線程安全的重要手段。線程安全涉及到對象兩個重要的狀態:共享性和可變性。若是對象是不可變的、線程私有的那麼它必定是線程安全的。因此說,只有在共享的、可變的對象上面進行操做時才須要加鎖,以保障線程安全。volatile和synchronized是java 5.0以前最先協調對象共享的機制。下面咱們將分別介紹他們:java
synchronized用來形容方法或者代碼塊,是java提供的最先的鎖機制,支持重入鎖。關於synchronized的詳細解析文章有不少,這裏列舉幾個注意事項:緩存
第一,使用synchronized關鍵字時必定要儘量的縮小範圍,儘量的在方法塊裏須要鎖的地方使用,而不是直接用來修飾整個方法。這須要咱們對方法裏面的操做進行分析,哪些須要加鎖,哪些不須要加鎖,只在須要鎖的地方加鎖,這樣便可以提升程序的效率,同時開放調用方法也減小了線程安全的隱患。安全
第二,synchronized提供的鎖機制是粗粒度的,當有線程訪問當前對象的synchronized方法或代碼塊時,其餘線程只能等待當前操做結束才能夠訪問。這聽上去彷佛存在必定的性能問題,但java 6.0之後synchronized在併發環境下性能獲得了大幅提高,所以建議儘量的使用synchronized,除非synchronized知足不了業務需求。並且synchronized使用時無需釋放鎖,並且JVM還提供了專門的優化支持,所以即便synchronized是古老的鎖,可是它依然適用於絕大多數場景。數據結構
volatile用來修飾變量保證其可見性。可見性是一種複雜的屬性,volatile變量不會被緩存在寄存器或者其餘處理器不可見的地方,在讀取volatile變量時返回的必定是最新寫入的值。volatile不是線程安全的,他不能替代鎖,它只在特定的場景下使用,使用時要很是當心。如下場景可使用volatile:併發
第一,對變量的寫入不依懶於變量當前的值,或者你能確保只有單個線程更新變量的值;性能
第二,該變量不會與其它狀態變量一塊兒歸入不變性條件中;優化
第三,在訪問變量時不須要加鎖。spa
ReentrantLock是一個可重入的互斥鎖,繼承自Lock接口。若是說synchronized是隱式鎖的話,那麼ReentrentLock就是顯式鎖。鎖的申請、使用、釋放都必須顯式的申明。Lock接口提供瞭如下方法:線程
public interface Lock{ void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time,TimeUnit unit) throws InterruptedException; void unLock(); Condition newCondition(); }
ReentrantLock實現了Lock接口,並提供了與synchronized相同的互斥性和內存可見性。那既然如此,爲何還要建立一個相似的鎖機制呢?內置鎖雖然好用,可是缺少一些靈活性,而ReentrantLock則能夠彌補這些不足。code
第一,輪詢鎖與定時鎖。tryLock方法實現了可定時的與可輪詢的鎖實現。與synchronized相比它有更完善的錯誤恢復機制。內置鎖中死鎖是一類嚴重的錯誤,只能重啓程序。而ReentrantLock可使用可定時或者輪詢的鎖,它會釋放已得到的鎖,而後再嘗試得到全部的鎖。在實現具備時間限制的操做時,定時鎖也很是也用。若是操做在給定時間內不能給出結果那麼就會使程序提早結束。
第二,可中斷鎖獲取操做。lockInterruptibly方法可以在得到鎖的同時保持對中斷的響應。並且因爲它包含在Lock中,所以無需建立其餘類型的不可中斷阻塞機制。
ReentrantReadWriteLock實現了一種標準的互斥讀寫鎖,繼承自ReadWriteLock。ReadWriteLock接口包含如下方法:
public interface ReadWriteLock{ Lock readLock(); Lock WriteLock(); }
ReentrantReadWriteLock咱們能夠這樣理解:當執行讀操做的時候能夠多個線程併發訪問;當執行寫操做的時候,只能夠同時被一個線程訪問。因此它使用的場景是讀操做多而寫操做少的併發場景。此外,ReentrantReadWriteLock還能夠設置是否爲公平鎖,是公平鎖的話則能夠按照排隊的順序獲取鎖,非公平鎖的話則是隨機得到。
上一節講到了ReentrantReadWriteLock實現了公平鎖和非公平鎖兩種模式。在公平鎖模式下線程按照請求的順序來得到鎖,而非公平模式下則能夠插隊。咱們的指望是全部的鎖都是公平的,畢竟插隊是一種很差的行爲。但實際上非公平鎖比公平鎖有着更高的併發效率。假設線程A持有一個鎖,而且線程B也請求這個鎖,因爲該鎖被線程A佔有,因此B線程掛起,當A使用結束時釋放鎖,此時喚醒B,B須要從新申請得到鎖。若是同時線程C也請求這個鎖,而且C極可能在B得到鎖以前已經得到、使用並釋放了鎖。這樣就實現了共贏,B線程得到的鎖沒有延遲同時線程C也獲得了執行,這提升了程序的吞吐率。
總結:與內置鎖相比,顯式鎖提供了一些擴展功能,在處理鎖的不可用方面有着更高的靈活性,而且對隊列有着更好的控制。可是顯式鎖沒法替換synchronized,只有在synchronized沒法知足需求時纔會使用它。讀寫鎖容許多個讀線程併發的訪問被保護對象,當訪問以讀取操做爲主的數據結構時,能提升程序的伸縮性。