目錄
1、悲觀鎖定
2、樂觀鎖
3、分散式鎖定
SET key unique_value  [EX seconds] [PX milliseconds] [NX|XX]
登入後複製
" >
SET key unique_value  [EX seconds] [PX milliseconds] [NX|XX]
登入後複製
5、自旋锁
6、独享锁
7、共享锁
8、读锁/写锁
9、公平锁/非公平锁
10、可中断锁/不可中断锁
11、分段鎖
12、鎖升級(無鎖|偏向鎖|輕量級鎖|重量級鎖)
無鎖
偏向鎖定
輕量級鎖定
重量级锁
13、锁优化技术(锁粗化、锁消除)
首頁 Java java教程 Java中鎖的實作方式有哪些

Java中鎖的實作方式有哪些

May 12, 2023 am 08:37 AM
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

熱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

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

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
突破或從Java 8流返回? 突破或從Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一種強大且表達力豐富的處理數據集合的方式。然而,使用Stream時,一個常見問題是:如何從forEach操作中中斷或返回? 傳統循環允許提前中斷或返回,但Stream的forEach方法並不直接支持這種方式。本文將解釋原因,並探討在Stream處理系統中實現提前終止的替代方法。 延伸閱讀: Java Stream API改進 理解Stream forEach forEach方法是一個終端操作,它對Stream中的每個元素執行一個操作。它的設計意圖是處

PHP:網絡開發的關鍵語言 PHP:網絡開發的關鍵語言 Apr 13, 2025 am 12:08 AM

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP與Python:了解差異 PHP與Python:了解差異 Apr 11, 2025 am 12:15 AM

PHP和Python各有優勢,選擇應基於項目需求。 1.PHP適合web開發,語法簡單,執行效率高。 2.Python適用於數據科學和機器學習,語法簡潔,庫豐富。

PHP與其他語言:比較 PHP與其他語言:比較 Apr 13, 2025 am 12:19 AM

PHP適合web開發,特別是在快速開發和處理動態內容方面表現出色,但不擅長數據科學和企業級應用。與Python相比,PHP在web開發中更具優勢,但在數據科學領域不如Python;與Java相比,PHP在企業級應用中表現較差,但在web開發中更靈活;與JavaScript相比,PHP在後端開發中更簡潔,但在前端開發中不如JavaScript。

PHP與Python:核心功能 PHP與Python:核心功能 Apr 13, 2025 am 12:16 AM

PHP和Python各有優勢,適合不同場景。 1.PHP適用於web開發,提供內置web服務器和豐富函數庫。 2.Python適合數據科學和機器學習,語法簡潔且有強大標準庫。選擇時應根據項目需求決定。

PHP的影響:網絡開發及以後 PHP的影響:網絡開發及以後 Apr 18, 2025 am 12:10 AM

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

Java程序查找膠囊的體積 Java程序查找膠囊的體積 Feb 07, 2025 am 11:37 AM

膠囊是一種三維幾何圖形,由一個圓柱體和兩端各一個半球體組成。膠囊的體積可以通過將圓柱體的體積和兩端半球體的體積相加來計算。本教程將討論如何使用不同的方法在Java中計算給定膠囊的體積。 膠囊體積公式 膠囊體積的公式如下: 膠囊體積 = 圓柱體體積 兩個半球體體積 其中, r: 半球體的半徑。 h: 圓柱體的高度(不包括半球體)。 例子 1 輸入 半徑 = 5 單位 高度 = 10 單位 輸出 體積 = 1570.8 立方單位 解釋 使用公式計算體積: 體積 = π × r2 × h (4

PHP:許多網站的基礎 PHP:許多網站的基礎 Apr 13, 2025 am 12:07 AM

PHP成為許多網站首選技術棧的原因包括其易用性、強大社區支持和廣泛應用。 1)易於學習和使用,適合初學者。 2)擁有龐大的開發者社區,資源豐富。 3)廣泛應用於WordPress、Drupal等平台。 4)與Web服務器緊密集成,簡化開發部署。

See all articles