在多線程或高併發情境中,常常會爲了保證數據一致性,而引入鎖機制,本文將爲各位帶來有關鎖的基本概念講解。關注個人公衆號「Java面典」瞭解更多 Java 相關知識點。html
根據鎖的各類特性,可將鎖分爲如下幾類:java
樂觀鎖與悲觀鎖並非特指某兩種類型的鎖,是人們定義出來的概念或思想,主要是指看待併發同步的角度。程序員
前提:認爲讀多寫少,遇到併發寫的可能性低,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖;算法
實現:在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,採起在寫時先讀出當前版本號,而後加鎖操做(比較跟上一次的版本號,若是同樣則更新),若是失敗則要重複讀-比較-寫的操做。編程
應用:在 Java 中 java.util.concurrent.atomic 包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS(Compare and Swap 【比較並交換】)實現的。CAS 是一種更新的原子操做,比較當前值跟傳入值是否同樣,同樣則更新,不然失敗。安全
前提:認爲寫多,遇到併發寫的可能性高,每次去拿數據的時候都認爲別人會修改;多線程
實現: 老是假設最壞的狀況,以每次在讀寫數據的時候都會上鎖,這樣別人想讀寫這個數據就會阻塞直到拿到鎖;併發
應用:Java中的 Synchronized 就是悲觀鎖,AQS 框架下的鎖則是先嚐試 CAS 樂觀鎖去獲取鎖,獲取不到,纔會轉換爲悲觀鎖,如 RetreenLock。框架
悲觀鎖適合寫操做很是多的場景,樂觀鎖適合讀操做很是多的場景,不加鎖會帶來大量的性能提高;jvm
悲觀鎖在 Java 中的使用,就是利用各類鎖;
樂觀鎖在 Java 中的使用,是無鎖編程,經常採用的是 CAS 算法,典型的例子就是原子類,經過 CAS 自旋實現原子操做的更新。
定義: 獨享鎖是指該鎖一次只能被一個線程所持有;
特色:獨佔鎖是一種悲觀保守的加鎖策略,它避免了讀/讀衝突,若是某個只讀線程獲取鎖,則其餘讀線程都只能等待,這種狀況下就限制了沒必要要的併發性,由於讀操做並不會影響數據的一致性。
應用:ReentrantLock 就是以獨佔方式實現的互斥鎖。
定義:共享鎖是指該鎖可同時被多個線程所持有,併發訪問、共享資源;
特色:共享鎖則是一種樂觀鎖,它放寬了加鎖策略,容許多個執行讀操做的線程同時訪問共享資源;
應用:
定義:可重入鎖,也叫作遞歸鎖,指的是同一線程外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。
應用:在 JAVA 環境下 ReentrantLock 和 synchronized 都是可重入鎖。
加鎖前檢查是否有排隊等待的線程,優先排隊等待的線程,先來先得。
加鎖時不考慮排隊等待問題,直接嘗試獲取鎖,獲取不到自動到隊尾等待。
分段鎖也並不是一種實際的鎖,而是一種思想 ConcurrentHashMap 是學習分段鎖的最好實踐。
這三種鎖是指鎖的狀態,而且是針對Synchronized。在Java 5經過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是經過對象監視器在對象頭中的字段來代表的。
指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。下降獲取鎖的代價。
指當鎖是偏向鎖的時候,被另外一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,提升性能。
指當鎖爲輕量級鎖的時候,另外一個線程雖然是自旋,但自旋不會一直持續下去,當自旋必定次數的時候,尚未獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能下降。
在Java中,自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗,缺點是循環會消耗CPU。
若是鎖的競爭激烈,或者佔用鎖時間長短的代碼塊,不適合使用自旋鎖。
同時有大量線程在競爭一個鎖,會致使獲取鎖的時間很長,線程自旋的消耗大於線程阻塞掛起操做的消耗,其它須要 CPU 的線程又不能獲取到 CPU,形成 CPU 的浪費。因此這種狀況下咱們要關閉自旋鎖。在 JDK1.5 及以前自旋時間是固定的,從 JDK1.6 開始,引入了適應性自旋鎖。
在Java中,須要謹慎使用鎖。如無必要,不用最好;必需要用的話,也須要儘量優化鎖的使用,以此來提升程序的吞吐量。關於鎖的優化,主要分爲應用方面的優化與 JVM 方面的優化,JVM方面的優化,通常不須要開發人員操心,開發人員更應該提高自身代碼素質,關注應用方面的優化。
Java多線程併發03——什麼是線程上下文,線程是如何調度的