搜尋
首頁Javajava教程Java中鎖的實作方式有哪些

1、悲觀鎖定

如其名,它是指對資料修改時持保守態度,認為其他人也會修改資料。因此在操作資料時,會把資料鎖住,直到操作完成。悲觀鎖在大多數情況下依靠資料庫的鎖機制實現,以確保操作最大程度的獨佔性。如果加鎖的時間太長,其他使用者長時間無法訪問,影響程式的並發存取性,同時這樣對資料庫效能開銷影響也很大,特別是長事務而言,這樣的開銷往往無法承受。

如果是單機系統,我們可以採用JAVA 自帶的 synchronized 關鍵字,透過添加到方法或同步區塊上,鎖住資源如果是分散式系統,我們可以藉助資料庫本身的鎖定機制來實現。

select * from 表名 where id= #{id} for update

使用悲觀鎖定的時候,我們要注意鎖的級別,MySQL innodb 在加鎖時,只有明確的指定主鍵或(索引欄位)才會使用 行鎖定;否則,會執行 表鎖,將整個表鎖住,此時效能會很差。在使用悲觀鎖定時,我們必須關閉 MySQL 資料庫的自動提交屬性,因為mysql預設使用自動提交模式。悲觀鎖適用於寫多的場景,而且並發效能要求不高。

2、樂觀鎖

樂觀鎖,從字面意思也能猜到個大概,在操作數據時非常樂觀,認為別人不會同時修改數據,因此樂觀鎖不會上鎖定只是在 提交更新 時,才會正式對資料的衝突與否進行偵測。如果發現衝突了,則回傳錯誤訊息,讓使用者決定如何做,fail-fast 機制 。否則,執行本次操作。

分為三個階段:資料讀取、寫入校驗、資料寫入。

如果是單機系統,我們可以基於JAVA 的 CAS來實現,CAS 是一種原子操作,借助硬體的比較並交換來實現。

如果是分散式系統,我們可以在資料庫表中增加一個 版本號 字段,如:version。

update 表 
set ... , version = version +1 
where id= #{id} and version = #{version}

操作前,先讀取記錄的版本號,更新時,透過SQL語句比較版本號是否一致。如果一致,則更新資料。否則會再次讀取版本,重試上面的操作。

3、分散式鎖定

JAVA 中的 synchronized 、ReentrantLock 等,都是解決單體應用單機部署的資源互斥問題。隨著業務快速發展,當單體應用演化為分散式叢集後,多執行緒、多進程分佈在不同的機器上,原來的單機並發控制鎖定策略失效

此時我們需要引入 分散式鎖,解決跨機器的互斥機制來控制共享資源的存取。

分散式鎖定需要具備哪些條件:

  • 與單機系統一樣的資源互斥功能,這是鎖定的基礎

  • 高效能取得、釋放鎖定

  • 高可用

  • #具備可重入性

  • 有鎖定失效機制,防止死鎖

  • 非阻塞,不管是否取得鎖,要能快速回傳

##實作方式多種多樣,基於 資料庫、Redis、以及 Zookeeper等,這裡講下主流的基於Redis的實現方式:

加鎖

SET key unique_value  [EX seconds] [PX milliseconds] [NX|XX]

透過原子指令,如果執行成功返回1,則表示加鎖成功。注意:unique_value 是客戶端產生的唯一標識,區分來自不同客戶端的鎖定操作 解鎖要特別注意,先判斷 unique_value 是不是加鎖的客戶端,是的話才允許解鎖刪除。畢竟我們不能刪除其他客戶端加的鎖。

解鎖:解鎖有兩個命令操作,需要藉助 Lua 腳本來保證原子性。

// 先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

借助 Redis 的高效能,Redis 實作分散式鎖定也是目前主流實作方式。但任何事情有利有弊,如果加鎖的伺服器宕機了,當slave 節點還來不及資料備份,那不是別的客戶端也可以獲得鎖。

為了解決這個問題,Redis 官方設計了一個分散式鎖定 Redlock。

基本想法:讓客戶端與多個獨立的Redis 節點並行請求申請加鎖,如果能在半數以上的節點成功地完成加鎖操作,那麼我們就認為,客戶端成功地獲得分佈式鎖,否則加鎖失敗。

4、可重入鎖

可重入鎖,也叫做遞歸鎖,是指在同一個執行緒在調外層方法取得鎖的時候,再進入內層方法會自動取得鎖。

物件鎖或類別鎖內部有計數器,一個執行緒每獲得一次鎖,計數器 1;解鎖時,計數器 -1。

有多少次加鎖,就要對應多少次解鎖,加鎖與解鎖成對出現。

Java 中的 ReentrantLock 和 synchronized 都是 可重入鎖定。可重入鎖的一個好處是可一定程度避免死鎖。

5、自旋锁

自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

自旋锁缺点:

  • 可能引发死锁。

  • 可能占用 CPU 的时间过长。

我们可以设置一个 循环时间 或 循环次数,超出阈值时,让线程进入阻塞状态,防止线程长时间占用 CPU 资源。JUC 并发包中的 CAS 就是采用自旋锁,compareAndSet 是CAS操作的核心,底层利用Unsafe对象实现的。

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

如果内存中 var1 对象的var2字段值等于预期的 var5,则将该位置更新为新值(var5 + var4),否则不进行任何操作,一直重试,直到操作成功为止。

CAS 包含了Compare和Swap 两个操作,如何保证原子性呢?CAS 是由 CPU 支持的原子操作,其原子性是在硬件层面进行控制。

特别注意,CAS 可能导致 ABA 问题,我们可以引入递增版本号来解决。

6、独享锁

独享锁,也有人叫它排他锁。无论读操作还是写操作,只能有一个线程获得锁,其他线程处于阻塞状态。

缺点:读操作并不会修改数据,而且大部分的系统都是 读多写少,如果读读之间互斥,大大降低系统的性能。下面的 共享锁 会解决这个问题。

像Java中的 ReentrantLock 和 synchronized 都是独享锁。

7、共享锁

共享锁是指允许多个线程同时持有锁,一般用在读锁上。读锁的共享锁可保证并发读是非常高效的。读写,写读 ,写写的则是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。

8、读锁/写锁

如果对某个资源是读操作,那多个线程之间并不会相互影响,可以通过添加读锁实现共享。如果有修改动作,为了保证数据的并发安全,此时只能有一个线程获得锁,我们称之为 写锁。读读是共享的;而 读写、写读 、写写 则是互斥的。

像 Java中的 ReentrantReadWriteLock 就是一种 读写锁。

9、公平锁/非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,先来先获取的公平性原则。

优点:所有的线程都能得到资源,不会饿死在队列中。

缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,CPU 唤醒下一个阻塞线程有系统开销。

Java中鎖的實作方式有哪些

非公平锁:多个线程不按照申请锁的顺序去获得锁,而是同时以插队方式直接尝试获取锁,获取不到(插队失败),会进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。

优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率会高点。

缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。

Java 多线程并发操作,我们操作锁大多时候都是基于 Sync 本身去实现的,而 Sync 本身却是 ReentrantLock 的一个内部类,Sync 继承 AbstractQueuedSynchronizer。

像 ReentrantLock 默认是非公平锁,我们可以在构造函数中传入 true,来创建公平锁。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

10、可中断锁/不可中断锁

可中断锁:指一个线程因为没有获得锁在阻塞等待过程中,可以中断自己阻塞的状态。不可中断锁:恰恰相反,如果锁被其他线程获取后,当前线程只能阻塞等待。如果持有锁的线程一直不释放锁,那其他想获取锁的线程就会一直阻塞。

内置锁 synchronized 是不可中断锁,而 ReentrantLock 是可中断锁。

ReentrantLock获取锁定有三种方式:

  • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于阻塞状态,直到该线程获取锁。

  • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。

  • tryLock(long timeout,TimeUnit unit), 如果取得了鎖定立即回傳true,如果別的執行緒正持有鎖,會等待參數給定的時間,在等待的過程中,如果取得了鎖定,就回傳true,如果等待逾時,回傳false。

  • lockInterruptibly(),如果取得了鎖定立即返回;如果沒有取得鎖定,則執行緒處於阻塞狀態,直到取得鎖定或執行緒被別的執行緒中斷。

11、分段鎖

分段鎖其實是一種鎖的設計,目的是細化鎖的粒度,並不是具體的一種鎖,對ConcurrentHashMap 而言,其並發的實作就是透過分段鎖定的形​​式來實現高效率的並發作業。

ConcurrentHashMap中的分段鎖定稱為Segment,它即類似HashMap(JDK7 中HashMap的實作)的結構,即內部擁有一個Entry數組,在數組中的每個元素又是一個鍊錶;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。

當需要put元素的時候,並不是對整個HashMap加鎖,而是先透過hashcode知道要放在哪一個分段中,然後對這個分段加鎖,所以當多執行緒put時,只要不是放在同一個分段中,可支援並行插入。

12、鎖升級(無鎖|偏向鎖|輕量級鎖|重量級鎖)

JDK 1.6之前,synchronized 還是重量級鎖,效率比較低。但在JDK 1.6後,JVM為了提高鎖的獲取與釋放效率對 synchronized 進行了優化,引入了偏向鎖和輕量級鎖,從此以後鎖的狀態就有了四種:無鎖、偏向鎖、輕量級級鎖、重量級鎖。這四種狀態會隨著競爭的情況逐漸升級,而且是不可降級。

Java中鎖的實作方式有哪些

無鎖

無鎖定並不會對資源鎖定,所有的執行緒都可以存取並修改同一個資源,但同時只有一個執行緒能修改成功。也就是我們常說的樂觀鎖。

偏向鎖定

偏向第一個存取鎖定的線程,初次執行synchronized程式碼區塊時,透過 CAS 修改物件頭裡的鎖定標誌位,鎖定物件變成偏向鎖定。

當一個執行緒存取同步程式碼區塊並取得鎖定時,會在 Mark Word 裡儲存鎖定偏向的執行緒 ID。在執行緒進入和退出同步區塊時不再透過 CAS 操作來加鎖和解鎖,而是偵測 Mark Word 裡是否儲存指向目前執行緒的偏向鎖。輕量級鎖的取得及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令即可。

執行完同步程式碼區塊後,執行緒並不會主動釋放偏向鎖定。當執行緒第二次再執行同步程式碼區塊時,執行緒會判斷此時持有鎖的執行緒是否就是自己(持有鎖的執行緒ID也在物件頭裡),如果是則正常往下執行。由於之前沒有釋放鎖,這裡不需要重新加鎖,偏向鎖幾乎沒有額外開銷,性能極高。

偏向鎖只有遇到其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖,執行緒是不會主動釋放偏向鎖的。關於偏向鎖的撤銷,需要等待全域安全點,也就是在某個時間點上沒有字節碼正在執行時,它會先暫停擁有偏向鎖的線程,然後判斷鎖物件是否處於被鎖定狀態。如果執行緒不處於活動狀態,則將物件頭設為無鎖狀態,並撤銷偏向鎖,恢復到無鎖(標誌位元為01)或輕量級鎖(標誌位元為00)的狀態。

偏向鎖是指當一段同步程式碼一直被同一個執行緒所存取時,也就是不存在多個執行緒的競爭時,那麼該執行緒在後續存取時就會自動取得鎖,從而降低取得鎖帶來的消耗。

輕量級鎖定

目前鎖定是偏向鎖,此時有多個執行緒同時來競爭鎖,偏向鎖定就會升級為輕量級鎖定。輕量級鎖定認為雖然競爭是存在的,但是理想情況下競爭的程度很低,透過自旋方式來取得鎖。

輕量級鎖定的取得有兩種情況:

  • 當關閉偏向鎖定功能時。

  • 多個執行緒競爭偏向鎖定導致偏向鎖定升級為輕量級鎖定。一旦有第二個執行緒加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。

在輕量級鎖定狀態下繼續鎖定競爭,沒有搶到鎖的執行緒將自旋,不停地循環判斷鎖是否能夠被成功取得。取得鎖的操作,其實就是透過CAS修改物件頭裡的鎖標誌位。先比較當前鎖定標誌位是否為“釋放”,如果是則將其設為“鎖定”,此過程是原子性。如果搶到鎖,然後線程將當前鎖的持有者資訊修改為自己。

重量级锁

如果线程的竞争很激励,线程的自旋超过了一定次数(默认循环10次,可以通过虚拟机参数更改),将轻量级锁升级为重量级锁(依然是 CAS  修改锁标志位,但不修改持有锁的线程ID),当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资。

13、锁优化技术(锁粗化、锁消除)

锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

举个例子:有个循环体,内部。

for(int i=0;i<size;i++){
    synchronized(lock){
        ...业务处理,省略
    }
}

经过锁粗化的代码如下:

synchronized(lock){
    for(int i=0;i<size;i++){
        ...业务处理,省略
    }
}

锁消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。

锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:

public String method() {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 10; i++) {
        sb.append("i:" + i);
    }
    return sb.toString();
}

以上代码经过编译之后的字节码如下:

Java中鎖的實作方式有哪些

从上述结果可以看出,之前我们写的线程安全的加锁的 StringBuffer 对象,在生成字节码之后就被替换成了不加锁不安全的 StringBuilder 对象了,原因是 StringBuffer 的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以我们可以使用锁消除(不加锁)来加速程序的运行。

以上是Java中鎖的實作方式有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
JVM如何促進Java的'寫作一次,在任何地方運行”(WORA)功能?JVM如何促進Java的'寫作一次,在任何地方運行”(WORA)功能?May 02, 2025 am 12:25 AM

JVM通過字節碼解釋、平台無關的API和動態類加載實現Java的WORA特性:1.字節碼被解釋為機器碼,確保跨平台運行;2.標準API抽像操作系統差異;3.類在運行時動態加載,保證一致性。

Java的較新版本如何解決平台特定問題?Java的較新版本如何解決平台特定問題?May 02, 2025 am 12:18 AM

Java的最新版本通過JVM優化、標準庫改進和第三方庫支持有效解決平台特定問題。 1)JVM優化,如Java11的ZGC提升了垃圾回收性能。 2)標準庫改進,如Java9的模塊系統減少平台相關問題。 3)第三方庫提供平台優化版本,如OpenCV。

說明JVM執行的字節碼驗證的過程。說明JVM執行的字節碼驗證的過程。May 02, 2025 am 12:18 AM

JVM的字節碼驗證過程包括四個關鍵步驟:1)檢查類文件格式是否符合規範,2)驗證字節碼指令的有效性和正確性,3)進行數據流分析確保類型安全,4)平衡驗證的徹底性與性能。通過這些步驟,JVM確保只有安全、正確的字節碼被執行,從而保護程序的完整性和安全性。

平台獨立性如何簡化Java應用程序的部署?平台獨立性如何簡化Java應用程序的部署?May 02, 2025 am 12:15 AM

Java'splatFormIndepentEncealLowsApplicationStorunonAnyOperatingsystemwithajvm.1)singleCodeBase:writeandeandcompileonceforallplatforms.2)easileupdates:updatebybytecodeforsimultanane deployment.3)testOnOneOnePlatForforurouniverSalpeforuluniverSalpehavior formafforulululyiversalivernave.444.44.444

Java的平台獨立性如何隨著時間的流逝而發展?Java的平台獨立性如何隨著時間的流逝而發展?May 02, 2025 am 12:12 AM

Java的平台獨立性通過JVM、JIT編譯、標準化、泛型、lambda表達式和ProjectPanama等技術不斷增強。自1990年代以來,Java從基本的JVM演進到高性能的現代JVM,確保了代碼在不同平台的一致性和高效性。

在Java應用程序中緩解平台特定問題的策略是什麼?在Java應用程序中緩解平台特定問題的策略是什麼?May 01, 2025 am 12:20 AM

Java如何緩解平台特定的問題? Java通過JVM和標準庫來實現平台無關性。 1)使用字節碼和JVM抽像操作系統差異;2)標準庫提供跨平台API,如Paths類處理文件路徑,Charset類處理字符編碼;3)實際項目中使用配置文件和多平台測試來優化和調試。

Java的平台獨立性與微服務體系結構之間有什麼關係?Java的平台獨立性與微服務體系結構之間有什麼關係?May 01, 2025 am 12:16 AM

java'splatformentenceenhancesenhancesmicroservicesharchitecture byferingDeploymentFlexible,一致性,可伸縮性和便攜性。 1)DeploymentFlexibilityAllowsibilityAllowsOllowsOllowSorlowsOllowsOllowsOllowSeStorunonAnyPlatformwithajvM.2)penterencyCrossServAccAcrossServAcrossServiCessImplifififiesDeevelopmentandeDe

GRAALVM與Java的平台獨立目標有何關係?GRAALVM與Java的平台獨立目標有何關係?May 01, 2025 am 12:14 AM

GraalVM通過三種方式增強了Java的平台獨立性:1.跨語言互操作,允許Java與其他語言無縫互操作;2.獨立的運行時環境,通過GraalVMNativeImage將Java程序編譯成本地可執行文件;3.性能優化,Graal編譯器生成高效的機器碼,提升Java程序的性能和一致性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中