高薪必备-锁
1、常见“锁”及使用方式一览表
2、各种锁代码案例
Lock
private readonly object _sync = new object();
public void UpdateState()
{
lock (_sync)
{
// 同步操作
}
}
//高阶实践-Double-check Locking(双重检查锁定)——实现线程安全的单例模式
public sealed class Singleton
{
private static volatile Singleton _instance;
private static readonly object _sync = new object();
private Singleton() { }
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (_sync)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
}
//使用 lock 实现缓存更新同步
public class CacheManager
{
private Dictionary<string, string> _cache = new Dictionary<string, string>();
private readonly object _sync = new object();
public void AddOrUpdate(string key, string value)
{
lock (_sync)
{
if (_cache.ContainsKey(key))
{
_cache[key] = value;
}
else
{
_cache.Add(key, value);
}
}
}
public string Get(string key)
{
lock (_sync)
{
return _cache.TryGetValue(key, out var value) ? value : null;
}
}
}
Monitor
Monitor.Enter(_sync);
try
{
// 同步代码
}
finally
{
Monitor.Exit(_sync);
}
//高阶用法
//1、带超时的进入锁(TryEnter)
private readonly object _sync = new object();
public bool TryAccess(int timeoutMs)
{
if (Monitor.TryEnter(_sync, timeoutMs))
{
try
{
Console.WriteLine("成功获取锁,执行操作...");
return true;
}
finally
{
Monitor.Exit(_sync);
}
}
else
{
Console.WriteLine("未能在指定时间内获取锁");
return false;
}
}
用途: 防止因等待锁而无限阻塞。
// 2、线程间通信:Wait / Pulse
private readonly object _sync = new object();
private bool _dataReady = false;
// 消费者线程
public void Consumer()
{
Monitor.Enter(_sync);
try
{
while (!_dataReady)
{
Console.WriteLine("消费者等待数据...");
Monitor.Wait(_sync); // 释放锁,等待通知
}
Console.WriteLine("数据就绪,开始处理");
}
finally
{
Monitor.Exit(_sync);
}
}
// 生产者线程
public void Producer()
{
Monitor.Enter(_sync);
try
{
Console.WriteLine("生产者正在准备数据...");
Thread.Sleep(1000); // 模拟耗时操作
_dataReady = true;
Monitor.Pulse(_sync); // 唤醒一个等待线程
}
finally
{
Monitor.Exit(_sync);
}
}
Wait() 必须放在 while 循环中,防止虚假唤醒。
Pulse() 只唤醒一个线程,PulseAll() 唤醒所有线程。
//自定义细粒度锁控制
private readonly object _sync = new object();
private int _value = 0;
public void UpdateValue(int newValue)
{
Monitor.Enter(_sync);
try
{
_value = newValue;
Console.WriteLine($"值已更新为: {_value}");
}
finally
{
Monitor.Exit(_sync);
}
}
public int GetValue()
{
Monitor.Enter(_sync);
try
{
return _value;
}
finally
{
Monitor.Exit(_sync);
}
}
Mutex
var mutex = new Mutex(false, "MyAppUniqueName");
if (mutex.WaitOne(TimeSpan.FromSeconds(3), false))
{
try
{
// 访问共享资源
}
finally
{
mutex.ReleaseMutex();
}
}
SemaPhore/SemaPhoreSlim
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3,5);
public async Task AccessResourceAsync()
{
await _semaphore.WaitAsync();
try
{
// 异步访问资源
}
finally
{
_semaphore.Release();
}
}
///异步超时访问(带超时机制)
bool acquired = await _semaphore.WaitAsync(TimeSpan.FromSeconds(2));
if (acquired)
{
try
{
// 成功获得资源
}
finally
{
_semaphore.Release();
}
}
else
{
Console.WriteLine("未能在指定时间内获取资源");
}
ReaderWriterLockSlim
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
public void ReadData()
{
_rwLock.EnterReadLock();
try
{
// 读取数据
}
finally
{
_rwLock.ExitReadLock();
}
}
public void WriteData()
{
_rwLock.EnterWriteLock();
try
{
// 写入数据
}
finally
{
_rwLock.ExitWriteLock();
}
}
SpinLock/SpinWait
SpinLock spinLock = new SpinLock();
bool lockTaken = false;
try
{
spinLock.Enter(ref lockTaken);
// 操作共享资源
}
finally
{
if (lockTaken) spinLock.Exit();
}
Concurrent Collections
ConcurrentDictionary<string, string> cache = new ConcurrentDictionary<string, string>();
cache.TryAdd("key", "value");
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
queue.Enqueue(1);
Channel<T>
var channel = Channel.CreateUnbounded<int>();
// 生产者
await channel.Writer.WriteAsync(1);
// 消费者
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine(item);
}
内存屏障
核心作用:防止重排序 + 保证内存可见性
🔍 问题背景:为什么需要内存屏障?
现代 CPU 和编译器为了提升性能,会做两件事:
指令重排序(Instruction Reordering):调整代码执行顺序(只要单线程结果不变);
缓存优化:每个 CPU 核心有自己的缓存,可能不立即同步到主内存。
但在多线程环境下,这些优化会导致一个线程看不到另一个线程的最新修改!
✅ 什么是内存屏障?
内存屏障是一种 CPU 指令,用来强制规定:屏障前后的读写操作不能跨过屏障重排序,并且确保内存操作对其他线程可见。
你可以把它想象成一道“闸门”:
闸门之前的操作必须全部完成;
之后的操作不能提前到闸门之前;
同时强制刷新缓存,让其他线程看到最新数据。
🌰 经典例子:双重检查锁定中的问题
_instance = new Singleton();
// 表面是一行,实际分三步:
// 1. 分配内存
// 2. 调用构造函数(初始化)
// 3. 将地址赋给 _instance
没有内存屏障时,CPU 可能把 步骤 3 提前到步骤 2 之前(重排序):
线程 A:先赋值 _instance = 地址(但对象还没初始化!)
线程 B:看到 _instance != null,直接使用 → 访问未初始化的对象!崩溃!
✅ volatile 如何引入内存屏障?
在 C# 中,对 volatile 字段的读写会自动插入内存屏障:
写操作(Write):插入 释放屏障(Release Fence) → 保证之前的写操作都完成后再写这个字段;
读操作(Read):插入 获取屏障(Acquire Fence) → 保证之后的读操作不会提前,且能看到最新的值。
所以:
private static volatile Singleton _instance;
这行代码确保:
_instance 的赋值不会被重排序到构造函数之前;
其他线程读 _instance 时,能看到完全初始化的对象。
线程安全 vs 内存屏障:关系总结
内存屏障是实现线程安全的底层手段之一;
但线程安全还可以通过锁(lock)、原子操作等其他方式实现;
lock 内部其实也使用了内存屏障(进入锁 = 获取屏障,退出锁 = 释放屏障)。
无评论