C#加鎖防止并發(fā)的幾種方法詳解
前言
在最近的工作中,有一個抽獎的需求。涉及到利益發(fā)放,這時候就需要加鎖,防止權(quán)益的重復(fù)發(fā)放,避免對客戶造成經(jīng)濟(jì)損失。在實際的工作中我用到的是Redis分布式鎖,借此機(jī)會我學(xué)習(xí)一下C#中各種加鎖的方式,有不對的地方,歡迎大家指正。
什么時候需要加鎖
- 多個線程訪問共享資源
當(dāng)多個線程訪問共享數(shù)據(jù)(例如共享的列表、字典、文件等)時,可能會導(dǎo)致數(shù)據(jù)競爭和不一致問題。加鎖可以確保在同一時刻,只有一個線程能夠修改共享資源,避免出現(xiàn)并發(fā)問題。
- 并發(fā)導(dǎo)致數(shù)據(jù)不一致問題
多線程并發(fā)執(zhí)行某個UPDATE語句,可能會導(dǎo)致數(shù)據(jù)不一致問題。
C#各種加鎖方式
主要介紹lock、Monitor 、SemaphoreSlim、Mutex、ReaderWriterLockSlim、Concurrent、Redis分布式鎖,看完之后其實不太需要考慮使用哪一種加鎖方式。因為每種加鎖都有特殊的適用場景。
- lock語句(互斥鎖)
lock語句用于實現(xiàn)互斥鎖,也是我們比較常見的一種加鎖方式。它是一種同步機(jī)制,用于確保多個線程在同一時間只能有一個線程進(jìn)入特定的代碼塊,其他線程則進(jìn)行等待,直到前一個線程釋放該鎖。lock細(xì)分的話,也有三種使用方式。
1、lock(this)
lock(this)鎖定的對象是當(dāng)前實例(就是該類的實例),但并不意味著該實例中的所有方法都會默認(rèn)加鎖,只有在該實例中的方法顯式lock加鎖,才能防止多個線程并發(fā)。
具體使用方法如下:
public class Test { public void Get() { lock (this) // 鎖定當(dāng)前實例 { //執(zhí)行代碼 } } }
Demo驗證
// demo internal class Program { static void Main(string[] args) { TestDemo testDemo = new TestDemo(); //開啟新線程,不然單線程執(zhí)行,沒有并發(fā),加鎖也沒有意義 Task.Run(() => { testDemo.Get(); }); Thread.Sleep(2000); //等待2秒,可以讓新線程先加鎖 testDemo.Set(); Console.ReadLine(); } } public class TestDemo { public void Get(int index) { lock (this) { Thread.Sleep(5000); Console.WriteLine("執(zhí)行完成,{index}"); } } public void Set() { lock (this) //顯式加鎖,不然還是可以并發(fā)執(zhí)行 { Console.WriteLine("Set完成"); } } }
執(zhí)行結(jié)果:
如果把Set方法中的鎖去掉,執(zhí)行結(jié)果肯定是反過來的,大家可以自己試一下。
2、lock(privateObj)
lock(privateObj)鎖的是一個私有的對象實例,它基于lock(this)的缺點,進(jìn)一步做了改善。privateObj 作為私有對象,只能在該類的內(nèi)部訪問,外部類無法訪問privateObj,避免了其他地方的代碼可能對鎖對象進(jìn)行干擾,減少死鎖的可能。
具體使用方法如下:
public class Test { private readonly object _lockObject = new object(); // 私有鎖對象 public void Get() { lock (_lockObject) // 鎖住私有對象 { //執(zhí)行代碼 } } }
Demo驗證
internal class Program { static void Main(string[] args) { TestDemo testDemo = new TestDemo(); Task.Run(() => { testDemo.Get(); }); Thread.Sleep(2000); testDemo.Set(); Console.ReadLine(); } } public class TestDemo { private readonly object _lockObject = new object(); public void Get() { lock (_lockObject) { Thread.Sleep(5000); Console.WriteLine("Get執(zhí)行完成"); } } public void Set() { lock (_lockObject) { Console.WriteLine("Set執(zhí)行完成"); } } }
對比lock(this)的代碼,其實沒有太大的改動。唯一的區(qū)別就是,lock(this)鎖住的是當(dāng)前類的實例,而lock(privateObj)鎖住的是該類內(nèi)部一個私有對象,鎖的粒度更小一點。????
3、lock(staticObj)
在實際工作中,上述兩種使用方式(lock(this) 和 lock(privateObj))并不常見。通過上面的代碼,我們可以發(fā)現(xiàn),不論是使用 lock(this) 還是 lock(privateObj),其本質(zhì)上是基于鎖住同一個對象實例來控制并發(fā)。如果并發(fā)時使用的對象實例不同,控制并發(fā)就會失效。例如,假設(shè)有 10 個用戶同時調(diào)用抽獎接口,每次調(diào)用都會實例化一個新的對象,這樣就會導(dǎo)致 10 個不同的對象實例,鎖并不會生效,仍然會發(fā)生并發(fā)問題。因此,常見的做法是使用 lock(staticObj),即通過鎖住靜態(tài)對象來確保多個請求之間的并發(fā)控制。
lock(staticObj)是鎖定一個靜態(tài)對象。由于靜態(tài)對象在類加載時只會被初始化一次,因此它是所有類實例共享的。
具體使用方法如下:
public class Test { private static readonly object _lockObject = new object(); // 靜態(tài)鎖對象 public void Get() { lock (_lockObject) // 鎖住靜態(tài)鎖對象 { //執(zhí)行代碼 } } }
Demo驗證
internal class Program { static void Main(string[] args) { //開啟十個線程,并行執(zhí)行Get方法 Parallel.For(0, 10, i => { new TestDemo().Get(); }); Console.ReadLine(); } } public class TestDemo { private static int _count = 0; private static readonly object _lockObject = new object(); public void Get() { lock (_lockObject) { _count += 1; Thread.Sleep(2000); //線程延遲,模擬處理數(shù)據(jù) Console.WriteLine($@"當(dāng)前值:{_count}"); } } }
輸出結(jié)果:
如果把鎖去掉,大家可以自行試一下,結(jié)果肯定不是這樣。
lock什么時候釋放鎖呢,就是lock塊中的代碼執(zhí)行完畢,會自動釋放該鎖。
lock有個很大一個缺點,lock塊中的代碼,不支持異步操作。
lock本質(zhì)上還是Monitor的語法糖,是在其基礎(chǔ)上包了一層,下面我會介紹一下Monitor。
對比
特性 | lock(this) | lock(privateObj) | lock(staticObj) |
鎖定對象 范圍 | 當(dāng)前實例對象 | 類內(nèi)部定義的私有對象 | 類級別的靜態(tài)對象 |
訪問范圍 | 外部代碼可以訪問實例對象并使用鎖,可能導(dǎo)致同步問題 | 僅限類內(nèi)部使用,避免外部訪問 | 所有類實例共享靜態(tài)對象,適用于跨實例同步 |
安全性 | 較差,容易導(dǎo)致外部代碼訪問并使用鎖對象 | 較好,不容易被外部濫用 | 跨實例同步,適合共享資源的同步 |
適用場景 | 單個實例的同步,通常不推薦外部訪問鎖對象 | 推薦用于類內(nèi)部資源的線程同步 | 適用于跨實例或跨線程的共享資源同步 |
死鎖風(fēng)險 | 較高,可能因外部代碼使用this 鎖導(dǎo)致 | 較低,避免了外部訪問鎖對象的問題 | 較低,但應(yīng)注意跨實例的鎖順序問題 |
Monitor 類(顯式鎖)
Monitor 也是 C# 中提供的一個用于線程同步的類,它保證在多線程程序中,只有一個線程可以同時訪問某個共享資源。上面說的lock,其實是個語法糖,它在編譯之后其實生成的代碼就是Monitor。相比較lock,Monitor提供了相對更多一些的擴(kuò)展功能。
具體使用方法如下:
public class Test { //私有靜態(tài)對象 private static object lockObj = new object(); public void Get() { Monitor.Enter(lockObj); //獲取鎖 try { //具體執(zhí)行邏輯 } catch (Exception ex) { } finally { Monitor.Exit(lockObj); // 釋放信號量 } } }
轉(zhuǎn)到定義,Monitor是一個靜態(tài)類。
稍微說一下其中幾個比較重要的方法。
- Enter:進(jìn)入鎖定區(qū)域,阻止其他線程進(jìn)入。
- Exit:退出鎖定區(qū)域,允許其他線程進(jìn)入。
- Wait:會釋放當(dāng)前線程的鎖,并讓該線程進(jìn)入等待隊列。當(dāng)前線程會被掛起,直到其他線程通知它繼續(xù)運行(使用 Pulse 或 PulseAll)
- Pulse:會喚醒等待該對象鎖的 一個線程。這個線程將會重新獲得鎖并繼續(xù)執(zhí)行。
- PulseAll:會喚醒 所有 等待該對象鎖的線程。所有被喚醒的線程會重新請求該鎖,并繼續(xù)執(zhí)行。
其中有兩個方法還是挺有意思的,可以先執(zhí)行線程A,然后讓線程A等待,喚醒線程B,線程B執(zhí)行完畢,再喚醒線程A。
基于上面說的,簡單實現(xiàn)一個小Demo
static async Task Main(string[] args) { Task.Run(() => { new TestDemo().Get(); }); Task.Run(() => { new TestDemo().Set(); }); Console.ReadLine(); } public class TestDemo { private static object lockObj = new object(); public void Get() { Monitor.Enter(lockObj); try { Console.WriteLine($"Get方法開始執(zhí)行"); //釋放當(dāng)前鎖,讓其他等待線程執(zhí)行 Monitor.Wait(lockObj, 1000); Console.WriteLine($"Get方法執(zhí)行完畢"); } catch (Exception ex) { } finally { // 確保鎖被釋放 Monitor.Exit(lockObj); } } public void Set() { Monitor.Enter(lockObj); try { Console.WriteLine($"Set方法開始執(zhí)行"); Thread.Sleep(1000); Console.WriteLine($"Set方法執(zhí)行完畢"); //喚醒等待的線程 Monitor.Pulse(lockObj); } catch (Exception ex) { } finally { // 確保鎖被釋放 Monitor.Exit(lockObj); } } }
執(zhí)行效果:
SemaphoreSlim(信號量)
SemaphoreSlim 是 .NET 提供的一種輕量級同步機(jī)制,用于限制并發(fā)線程數(shù)。它的工作原理類似于操作系統(tǒng)級的信號量,但相比于 Semaphore,SemaphoreSlim 更加高效且適合于高性能應(yīng)用程序,因為它主要用于應(yīng)用程序內(nèi)部的線程同步,并且不會像操作系統(tǒng)信號量那樣依賴操作系統(tǒng)內(nèi)核,因此在多數(shù)情況下性能更好。
SemaphoreSlim主要的功能是,它可以控制指定多少個線程同時訪問共享資源。換句話說,它可以控制并發(fā)執(zhí)行的數(shù)量,并不是某個方法同一時刻只能有一個線程執(zhí)行(SemaphoreSlim 也能支持,將并發(fā)數(shù)設(shè)置為1即可)。
具體使用方法如下:
public class Test { //靜態(tài)信號鎖,同時可以3個線程同時訪問 private static readonly SemaphoreSlim semaphore = new SemaphoreSlim(3); public void Get() { semaphore.Wait(); //獲取鎖 try { //具體執(zhí)行邏輯 } catch (Exception ex) { } finally { semaphore.Release(); // 釋放信號量 } } }
具體使用方法,大家可以轉(zhuǎn)到定義進(jìn)去看看,其中Wait可以設(shè)置值,如果獲取鎖失敗,可以返回,其他線程就不需要等待了。
下面我用工作中用到的SemaphoreSlim ,作為Demo,場景就是同一時刻同一個二維碼只能有一個人可以參與,防止并發(fā),導(dǎo)致獎項超量發(fā)出。
public class TestDemo { // 使用 SemaphoreSlim + ConcurrentDictionary 來保護(hù)共享資源 private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>(); //獲取鎖 private static SemaphoreSlim GetLock(string key) { return _locks.GetOrAdd(key, new SemaphoreSlim(1, 1)); } public async Task Get(string key) { var semaphore = GetLock(key); var lockAcquired = false; try { lockAcquired = semaphore.Wait(0); // 嘗試立即獲取信號量 if (!lockAcquired) return; //獲取鎖失敗,返回,不會等待上一個加鎖的線程釋放 //獲取鎖成功,執(zhí)行以下邏輯 } catch (Exception ex) { } finally { if (lockAcquired) { // 釋放鎖 semaphore.Release(); // 移除鎖(防止內(nèi)存泄漏) _locks.TryRemove(key, out _); } } } }
上述這個例子,是沒用Redis分布式鎖之前,經(jīng)常用的一種加鎖方式。與lock相比,SemaphoreSlim這個可以使用異步,也可以限制線程并發(fā)的數(shù)量,適用的功能場景也更多。
lock不需要手動釋放鎖,執(zhí)行完代碼塊里的內(nèi)容,會自動釋放。但SemaphoreSlim需要調(diào)用Release方法(一定要放到finally中),顯式釋放鎖。
Mutex(互斥體)
Mutex也是一種線程同步機(jī)制,也可以控制多個線程對共享資源的訪問。與之前說的lock和SemaphoreSlim有一個非常大的一個不同點,Mutex是可以跨進(jìn)程使用的,之前說的兩個,只能在一個進(jìn)程中控制并發(fā)。
具體使用方法如下:
public class Test { //創(chuàng)建一個 Mutex鎖 private static Mutex mutex = new Mutex(); public void Get() { //獲取 Mutex鎖 mutex.WaitOne(); try { //具體執(zhí)行邏輯 } catch (Exception ex) { } finally { //釋放 Mutex鎖 mutex.ReleaseMutex(); } } }
Mutex如果不需要跨進(jìn)程防止并發(fā),使用方法也很簡單。無非就是加鎖、獲取鎖、釋放鎖,這里就不再寫代碼演示了,下面主要寫一個跨進(jìn)程的Demo。
internal class Program { static void Main(string[] args) { new TestDemo().Get(); Console.WriteLine("全部執(zhí)行完畢"); Console.ReadLine(); } } public class TestDemo { Mutex mutex = new Mutex(false, "Global\\MutexTest"); public void Get() { mutex.WaitOne(); try { Thread.Sleep(30000); Console.WriteLine($"Get執(zhí)行完成"); } catch (Exception ex) { } finally { mutex.ReleaseMutex(); } } }
Mutex如果需要跨進(jìn)行限制并發(fā),需要加上一個名稱。命名需要以下注意事項:
- 命名時建議使用 Global\ 或 Local\ 前綴,以明確 Mutex 的作用范圍。
- 在 Windows 系統(tǒng)上,Global\ 適用于所有會話,Local\ 僅限當(dāng)前會話。
大家可以再創(chuàng)建一個控制臺程序,然后實例化一個Mutex,名稱保持一致,啟動執(zhí)行,會發(fā)現(xiàn)不會立馬執(zhí)行。需要等待另一個控制臺程序中的Mutex釋放鎖。這里我就不把我另一個控制臺的代碼放出來,很簡單。
將Mutex轉(zhuǎn)到定義可以看出,Mutex是Threading命名空間下的,并且不支持異步。
ReaderWriterLockSlim(讀寫鎖)
ReaderWriterLockSlim聽名字就知道,該鎖有兩種模式,讀模式和寫模式,它允許多個線程以讀模式同時訪問共享資源,但只有一個線程能夠以寫模式訪問資源。ReaderWriterLockSlim 提供了比傳統(tǒng)的 Monitor 或 lock 更細(xì)粒度的控制,特別適合高并發(fā)讀操作和低頻寫操作的場景。
具體使用方法如下:
public class Test { //創(chuàng)建一個 ReaderWriterLockSlim 鎖 private static readonly ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); public void Get() { //獲取 讀鎖 lockSlim.EnterReadLock(); //獲取 寫鎖 lockSlim.EnterWriteLock(); try { //具體執(zhí)行邏輯 } catch (Exception ex) { } finally { //釋放鎖 lockSlim.ExitReadLock(); lockSlim.ExitWriteLock(); } } }
- 讀鎖(Read Lock):允許多個線程并發(fā)地讀取共享資源。只要沒有線程持有寫鎖,多個線程可以同時獲取讀鎖。
- 寫鎖(Write Lock):寫鎖是獨占的,只有一個線程能夠獲取寫鎖。如果任何線程持有讀鎖或?qū)戞i,其他線程就不能獲得寫鎖。
- 鎖的升級與降級:ReaderWriterLockSlim 允許線程將鎖從讀鎖升級到寫鎖(通過 EnterUpgradeableReadLock),但不能將寫鎖降級為讀鎖。
轉(zhuǎn)到定義看下相關(guān)方法
從源碼中可以看出,獲取鎖可以定時超時時間,還有是否獲取到鎖的屬性、等待讀鎖或者寫鎖的數(shù)量等等,在實際用到時,大家可以再看看。
下面我根據(jù)可升級讀鎖,做一個Demo
internal class Program { static void Main(string[] args) { Task.Run(() => { new TestDemo().WriteData(); }); Thread.Sleep(1000); Task.Run(() => { new TestDemo().ReadData(); }); Console.WriteLine("全部執(zhí)行完畢"); Console.ReadLine(); } } public class TestDemo { private static readonly ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); public void ReadData() { lockSlim.EnterReadLock(); try { Console.WriteLine("開始執(zhí)行讀鎖"); Thread.Sleep(1000); Console.WriteLine("執(zhí)行讀鎖結(jié)束"); } catch (Exception) { } finally { lockSlim.ExitReadLock(); } } public void WriteData() { //是一個可升級的讀鎖 lockSlim.EnterUpgradeableReadLock(); try { try { Console.WriteLine("開始執(zhí)行寫鎖"); //升級為寫鎖 lockSlim.EnterWriteLock(); Thread.Sleep(10000); Console.WriteLine("寫鎖執(zhí)行完畢"); } catch (Exception) { } finally { lockSlim.ExitWriteLock(); } } catch (Exception) { } finally { lockSlim.EnterUpgradeableReadLock(); } } }
需要注意的是,ReaderWriterLockSlim中的方法都是成對出現(xiàn)的,必須在finally中釋放鎖。
Concurrent 集合
Concurrent 集合是為了在多線程環(huán)境下提供線程安全的數(shù)據(jù)結(jié)構(gòu),避免顯式加鎖的復(fù)雜性。System.Collections.Concurrent 命名空間提供了幾種常用的線程安全集合類,如 ConcurrentDictionary、ConcurrentQueue、ConcurrentStack、BlockingCollection 等。
這些集合通過內(nèi)部機(jī)制確保了多線程訪問時的數(shù)據(jù)一致性,并盡可能避免鎖操作的使用,提升了性能。
- ConcurrentDictionary
ConcurrentDictionary是一個線程安全的字典類型,用于存儲鍵值對。它允許多個線程同時讀寫,保證在高并發(fā)環(huán)境下的數(shù)據(jù)一致性。與 Dictionary<TKey, TValue> 不同,ConcurrentDictionary 內(nèi)部實現(xiàn)了更高效的并發(fā)訪問機(jī)制。
- ConcurrentQueue<T>
ConcurrentQueue<T> 是一個線程安全的隊列,它采用先進(jìn)先出(FIFO)原則,適用于多個線程同時操作隊列時使用。支持多個線程進(jìn)行并發(fā)的入隊和出隊操作。
- ConcurrentStack<T>
ConcurrentStack<T> 是一個線程安全的棧,采用后進(jìn)先出(LIFO)原則。多個線程可以并發(fā)地執(zhí)行 Push 和 Pop 操作而不需要顯式的鎖。
- BlockingCollection<T>
BlockingCollection<T> 是一個線程安全的集合類,基于 IProducerConsumerCollection<T> 接口實現(xiàn)。它允許你在多個線程之間進(jìn)行生產(chǎn)者-消費者模式的操作,并提供阻塞和超時的機(jī)制。如果集合已滿或為空,調(diào)用 Add 或 Take 方法的線程會被阻塞,直到集合中有空間或元素可用。
- ConcurrentBag<T>
ConcurrentBag<T> 是一個線程安全的集合,適用于多個線程需要無序地向集合中添加和從集合中刪除元素的場景。與其他并發(fā)集合不同,ConcurrentBag<T> 不保證元素的順序,它更適用于那些不關(guān)心順序的場景。
大家需要用到了,可以再詳細(xì)了解一下,我用到了ConcurrentDictionary,這個比較簡單,我就不做例子了。
Redis分布式鎖
上面介紹的加鎖方式,都是在同一個進(jìn)程或多個進(jìn)程下,還局限于同一個服務(wù)器。那如果程序是多個服務(wù)器分布式部署,那么以上的加鎖方式肯定就失效了。解決方案就是用Redis分布式鎖。
Redis 分布式鎖是一種常用的分布式同步機(jī)制,適用于需要多個服務(wù)協(xié)調(diào)訪問共享資源的場景。Redis 分布式鎖核心是通過 Redis 提供的 原子性操作 來確保多個客戶端在分布式系統(tǒng)中對共享資源的互斥訪問。確保在多個分布式進(jìn)程或節(jié)點之間,每次只有一個客戶端能夠獲得對某個資源的訪問權(quán),防止資源沖突。
需要引用StackExchange.Redis包,下面是一個簡易的Demo
static async Task Main(string[] args) { Parallel.For(0, 50, async i => { // 連接 Redis var redis = ConnectionMultiplexer.Connect("localhost"); // 創(chuàng)建鎖管理對象 var distributedLock = new RedisDistributedLock(redis); // 鎖標(biāo)識和唯一值 string lockKey = "CustomerRedisLock"; string lockValue = Guid.NewGuid().ToString(); TimeSpan lockTimeout = TimeSpan.FromSeconds(10); // 嘗試獲取鎖 bool isLockAcquired = await distributedLock.AcquireLockAsync(lockKey, lockValue, lockTimeout); if (isLockAcquired) { Console.WriteLine("鎖已獲取,執(zhí)行任務(wù)中..."); try { // 模擬任務(wù)執(zhí)行 await Task.Delay(2000); } finally { // 釋放鎖 bool isLockReleased = await distributedLock.ReleaseLockAsync(lockKey, lockValue); Console.WriteLine(isLockReleased ? "鎖已釋放" : "釋放鎖失敗或鎖已過期"); } } else { Console.WriteLine("未能獲取鎖"); } }); Console.ReadLine(); } public class RedisDistributedLock { private readonly IDatabase _redisDatabase; private readonly TimeSpan _defaultLockTimeout = TimeSpan.FromSeconds(10); // 默認(rèn)鎖超時時間 public RedisDistributedLock(IConnectionMultiplexer redisConnection) { _redisDatabase = redisConnection.GetDatabase(); } //獲取鎖 public async Task<bool> AcquireLockAsync(string key, string value, TimeSpan? timeout = null) { var lockTimeout = timeout ?? _defaultLockTimeout; return await _redisDatabase.StringSetAsync(key, value, lockTimeout, When.NotExists); } //釋放鎖 public async Task<bool> ReleaseLockAsync(string key, string value) { // 獲取當(dāng)前鎖的值 var currentValue = await _redisDatabase.StringGetAsync(key); // 如果當(dāng)前鎖的值和傳入的值相等,則釋放鎖 if (currentValue == value) { return await _redisDatabase.KeyDeleteAsync(key); } return false; // 鎖值不匹配,表示鎖已經(jīng)被其他客戶端持有 } }
大家自己可以動手封裝一下,這只是簡易的版本。
總結(jié)
- lock
最簡單的加鎖方式,是一個語法糖。缺點就是代碼塊中不支持異步,并且語法比較單一。
Monitor:
短期內(nèi)需要對共享資源進(jìn)行排他訪問,用于小范圍的同步,性能較好。支持線程等待和喚醒。
缺點是不能控制鎖的粒度(只能針對方法或代碼塊)
- SemaphoreSlim
可以限制并發(fā)訪問的數(shù)量,缺點是只能在同一進(jìn)程中使用,不能用于跨進(jìn)程同步
- Mutex
用于跨進(jìn)程同步,適合一些需要在不同進(jìn)程中進(jìn)行互斥操作的場景,例如文件操作、共享內(nèi)存等。缺點是由于涉及到操作系統(tǒng)調(diào)用,性能開銷較大。
- ReaderWriterLockSlim
控制鎖的粒度更細(xì),適用讀多寫少的場景。如果你有一個資源,讀操作比寫操作多,使用 ReaderWriterLockSlim 可以顯著提高并發(fā)性能。
- Concurrent
當(dāng)多個線程需要并發(fā)地訪問某個集合時,使用并發(fā)集合可以避免手動管理鎖。適用于高并發(fā)的環(huán)境,特別是當(dāng)你需要高效地進(jìn)行元素插入、刪除和查詢時。
- Redis分布式鎖
支持跨進(jìn)程或跨服務(wù)器的分布式同步,如果你有多個服務(wù)/服務(wù)器需要同步某些操作,可以使用 Redis 分布式鎖。適用于分布式系統(tǒng)中的任務(wù)調(diào)度、資源共享、任務(wù)獨占等場景。
Redis 分布式鎖需要注意處理超時、鎖釋放等問題,通常會加上超時機(jī)制避免死鎖。
怎樣加鎖需要考慮具體的場景,也可能兩種加鎖方式一起使用。
加鎖的注意事項:
- 加鎖的時間盡量縮短,所以不必要的代碼,不要放到加鎖的代碼塊中。
- 在控制并發(fā)時,優(yōu)先選擇 Concurrent 集合類。
- 避免鎖的嵌套,多個鎖嵌套使用時,容易導(dǎo)致死鎖和性能問題。
- 選擇合適的鎖粒度,粗粒度鎖簡單易用,但性能較差。細(xì)粒度鎖性能高,但編碼復(fù)雜度高。
- 設(shè)置超時機(jī)制,避免讓線程在加鎖時長時間等待,特別是對于高并發(fā)場景,可能導(dǎo)致資源浪費。
到此這篇關(guān)于C#、加鎖防止并發(fā)的幾種方法的文章就介紹到這了,更多相關(guān)C#加鎖防止并發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#日期格式強(qiáng)制轉(zhuǎn)換方法(推薦)
下面小編就為大家分享一C#日期格式強(qiáng)制轉(zhuǎn)換的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11C#實現(xiàn)兩個richtextbox控件滾動條同步滾動的簡單方法
這篇文章主要給大家介紹了C#實現(xiàn)兩個richtextbox控件滾動條同步滾動的簡單方法,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-05-05C#調(diào)用C++DLL傳遞結(jié)構(gòu)體數(shù)組的終極解決方案
這篇文章主要介紹了C#調(diào)用C++DLL傳遞結(jié)構(gòu)體數(shù)組的終極解決方案的相關(guān)資料,需要的朋友可以參考下2017-01-01快速解決C# android base-64 字符數(shù)組的無效長度問題
下面小編就為大家?guī)硪黄焖俳鉀QC# android base-64 字符數(shù)組的無效長度問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08