详解 .net9 内置 Lock 对象,更加现代化和灵活可控的锁对象

发布于:2025-05-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

.NET 9 引入了全新的 System.Threading.Lock 类型,作为更现代、类型安全且具备递归支持的同步原语。与传统的基于 Monitor.Enter/lock(obj) 的方式不同,Lock 是一个具体的类,提供了更灵活的 API 和结构化编程模型。

  • Lock 类

Lock 是一个具体密封类,详细代码如下:

#region 程序集 System.Runtime, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\9.0.5\ref\net9.0\System.Runtime.dll
#endregion


namespace System.Threading
{
    //
    // 摘要:
    //     Provides a mechanism for achieving mutual exclusion in regions of code between
    //     different threads.
    public sealed class Lock
    {
        //
        // 摘要:
        //     Initializes a new instance of the System.Threading.Lock class.
        public Lock();

        //
        // 摘要:
        //     Gets a value that indicates whether the lock is held by the current thread.
        //
        // 返回结果:
        //     true if the current thread holds the lock; otherwise, false.
        public bool IsHeldByCurrentThread { get; }

        //
        // 摘要:
        //     Enters the lock, waiting if necessary until the lock can be entered.
        //
        // 异常:
        //   T:System.Threading.LockRecursionException:
        //     The lock has reached the limit of repeated entries by the current thread. The
        //     limit is implementation-defined and is intended to be high enough that it would
        //     not be reached in normal situations.
        public void Enter();
        //
        // 摘要:
        //     Enters the lock, waiting if necessary until the lock can be entered.
        //
        // 返回结果:
        //     A System.Threading.Lock.Scope that can be disposed to exit the lock.
        //
        // 异常:
        //   T:System.Threading.LockRecursionException:
        //     The lock has reached the limit of repeated entries by the current thread. The
        //     limit is implementation-defined and is intended to be high enough that it would
        //     not be reached in normal situations.
        public Scope EnterScope();
        //
        // 摘要:
        //     Exits the lock.
        //
        // 异常:
        //   T:System.Threading.SynchronizationLockException:
        //     The current thread does not hold the lock.
        public void Exit();
        //
        // 摘要:
        //     Tries to enter the lock without waiting.
        //
        // 返回结果:
        //     true if the lock was entered by the current thread; otherwise, false.
        //
        // 异常:
        //   T:System.Threading.LockRecursionException:
        //     The lock has reached the limit of repeated entries by the current thread. The
        //     limit is implementation-defined and is intended to be high enough that it would
        //     not be reached in normal situations.
        public bool TryEnter();
        //
        // 摘要:
        //     Tries to enter the lock, waiting if necessary for the specified number of milliseconds
        //     until the lock can be entered.
        //
        // 参数:
        //   millisecondsTimeout:
        //     The number of milliseconds to wait until the lock can be entered. Specify Timeout.Infinite
        //     (
        //
        //     -1
        //
        //     ) to wait indefinitely, or
        //
        //     0
        //
        //     to not wait.
        //
        // 返回结果:
        //     true if the lock was entered by the current thread; otherwise, false.
        //
        // 异常:
        //   T:System.ArgumentOutOfRangeException:
        //     millisecondsTimeout is less than
        //
        //     -1
        //
        //     .
        //
        //   T:System.Threading.LockRecursionException:
        //     The lock has reached the limit of repeated entries by the current thread. The
        //     limit is implementation-defined and is intended to be high enough that it would
        //     not be reached in normal situations.
        public bool TryEnter(int millisecondsTimeout);
        //
        // 摘要:
        //     Tries to enter the lock, waiting if necessary until the lock can be entered or
        //     until the specified timeout expires.
        //
        // 参数:
        //   timeout:
        //     A System.TimeSpan that represents the number of milliseconds to wait until the
        //     lock can be entered. Specify a value that represents Timeout.Infinite (
        //
        //     -1
        //
        //     ) milliseconds to wait indefinitely, or a value that represents
        //
        //     0
        //
        //     milliseconds to not wait.
        //
        // 返回结果:
        //     true if the lock was entered by the current thread; otherwise, false.
        //
        // 异常:
        //   T:System.ArgumentOutOfRangeException:
        //     timeout, after its conversion to an integer millisecond value, represents a value
        //     that is less than
        //
        //     -1
        //
        //     milliseconds or greater than Int32.MaxValue milliseconds.
        //
        //   T:System.Threading.LockRecursionException:
        //     The lock has reached the limit of repeated entries by the current thread. The
        //     limit is implementation-defined and is intended to be high enough that it would
        //     not be reached in normal situations.
        public bool TryEnter(TimeSpan timeout);

        //
        // 摘要:
        //     Represents a System.Threading.Lock that might have been entered.
        public ref struct Scope
        {
            //
            // 摘要:
            //     Exits the lock if the System.Threading.Lock.Scope represents a lock that was
            //     entered.
            //
            // 异常:
            //   T:System.Threading.SynchronizationLockException:
            //     The System.Threading.Lock.Scope represents a lock that was entered and the current
            //     thread does not hold the lock.
            public void Dispose();
        }
    }
}

🔒 Lock 对象概述

System.Threading.Lock 是一种 轻量级互斥锁(Lightweight Mutex,用于控制多线程对共享资源的访问,确保同一时间只有一个线程可以执行特定代码段

它适用于需要 显式管理加锁和解锁操作 的场景,比如在 非阻塞编程 中尝试获取锁或使用 超时机制

✅ 特性

  • 支持递归锁定(默认允许同一个线程多次进入)
  • 提供阻塞、非阻塞和带超时的获取方式
  • 使用 Scope 结构实现 RAII 风格的自动释放
  • 线程亲和性强,可判断当前线程是否持有锁

RAII (Resource Acquisition Is Initialization) 是一种 C++ 编程范式,其核心思想是 将资源的生命周期绑定到对象的生命周期上,即:

  • 资源的获取发生在对象构造时;
  • 资源的释放发生在对象析构时。

.NET 9System.Threading.Lock 中,Scope 是一个 ref struct,通过调用 EnterScope() 方法获得。它实现了类似 RAII 的模式:

//
// 摘要:
//     Represents a System.Threading.Lock that might have been entered.
public ref struct Scope
{
    //
    // 摘要:
    //     Exits the lock if the System.Threading.Lock.Scope represents a lock that was
    //     entered.
    //
    // 异常:
    //   T:System.Threading.SynchronizationLockException:
    //     The System.Threading.Lock.Scope represents a lock that was entered and the current
    //     thread does not hold the lock.
    public void Dispose();
}

// 使用 EnterScope()
using (var scope = myLock.EnterScope())
{
    // 执行临界区代码
} // 自动调用 scope.Dispose(),进而释放锁
解释 RAII 风格的自动释放

使用 Scope 结构实现 RAII 风格的自动释放 这句话的意思是:

  • 当你调用 EnterScope() 获取一个 Scope 实例时,锁已经被当前线程持有
  • 将该 Scope 实例放入 using 语句块中,当代码块结束时,会自动调用其 Dispose() 方法
  • Dispose() 方法内部,会调用 Exit() 来释放锁;
  • 这样就实现了 锁的获取和释放与代码块的进入和退出严格绑定,避免忘记释放锁或异常情况下锁未被释放的问题。

这种方式提升了代码的安全性和可读性,是现代 .NET 推荐使用的同步编程模型。

📌 构造函数

public Lock();

初始化一个新的 Lock 实例。默认情况下,该锁是可重入的(recursive),即同一个线程可以多次调用 Enter() 而不会死锁。


🧱 主要方法详解

1. void Enter()

阻塞当前线程直到成功获取锁。

var myLock = new Lock();

myLock.Enter(); // 阻塞直到获取锁
try {
    // 访问共享资源
} finally {
    myLock.Exit();
}
抛出异常:
  • LockRecursionException: 如果递归次数超过限制(极少发生)

2. Scope EnterScope()

尝试进入锁,并返回一个 ref struct Scope,用于通过 using 自动释放锁。

using (myLock.EnterScope())
{
    // 安全访问共享资源
}
// 锁自动释放

这是推荐的方式,避免手动调用 Exit() 导致的资源泄漏。


3. void Exit()

释放锁。必须由当前持有锁的线程调用,否则抛出异常。

myLock.Enter();
try {
    // ...
} finally {
    myLock.Exit();
}
抛出异常:
  • SynchronizationLockException: 当前线程未持有锁

4. bool TryEnter()

尝试立即获取锁,不等待。

if (myLock.TryEnter())
{
    try {
        // 成功获取锁
    } finally {
        my7.Exit();
    }
}
else
{
    // 获取失败,跳过
}

返回值:

  • true: 成功获取锁
  • false: 锁已被其他线程占用

5. bool TryEnter(int millisecondsTimeout)

尝试在指定时间内获取锁。

bool lockTaken = myLock.TryEnter(1000); // 最多等待1秒
if (lockTaken)
{
    try { /* ... */ } finally { myLock.Exit(); }
}

参数:

  • millisecondsTimeout: 等待毫秒数,-1 表示无限等待,0 表示不等待

返回值:

  • true: 成功获取锁
  • false: 超时或未获取到

6. bool TryEnter(TimeSpan timeout)

同上,但接受 TimeSpan 参数。

bool lockTaken = myLock.TryEnter(TimeSpan.FromSeconds(2));

🧩 嵌套结构:Lock.Scope

这是一个 ref struct,用于封装锁的生命周期,推荐配合 using 使用。

using var scope = myLock.EnterScope();
// 执行临界区代码

scopedispose 时,会自动调用 Exit()

方法:
public void Dispose();

释放锁,若当前线程未持有锁则抛出异常。


🧠 内部原理简析

  • Lock 内部基于高效的自旋锁(SpinLock)+ 内核事件(Event)混合实现。
  • 初期尝试自旋几次以快速获取锁,失败后进入内核等待状态。
  • 支持递归锁定,默认递归深度限制较高(足够日常使用)。
  • 使用线程本地存储记录当前线程是否持有锁,保证 IsHeldByCurrentThread 的准确性。

🔍 属性:bool IsHeldByCurrentThread

检查当前线程是否持有该锁。

if (myLock.IsHeldByCurrentThread)
{
    Console.WriteLine("当前线程已持有锁");
}

适用于调试和日志记录,避免重复加锁导致死锁。


🧪 应用场景举例

场景 1:线程安全的计数器

private int _counter = 0;
private readonly Lock _lock = new();

public void Increment()
{
    using (_lock.EnterScope())
    {
        _counter++;
    }
}

public int GetCount()
{
    using (_lock.EnterScope())
    {
        return _counter;
    }
}

场景 2:线程安全的单例实现

  • 方式一:原生 Lock 实现
public class Singleton
{
    // 单例实例
    private static Singleton _instance;

    // 锁对象
    private static readonly Lock _lock = new();

    // 私有化无参构造函数
    private Singleton()
    {
        // 初始化逻辑
    }

    // 获取单例实例的方法
    public static Singleton Instance
    {
        get
        {
            // 先判断是否已创建,避免每次都加锁
            if (_instance == null)
            {
                using (_lock.EnterScope())
                {
                    // 再次检查是否为 null(双重检查锁定)
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }

            return _instance;
        }
    }
    // 其他方法实现
}
  • 方式二:使用 Lazy<T> & Lock 实现
public class Singleton
{
    private static readonly Lock _lock = new();
    private static readonly Lazy<Singleton> _lazyInstance = new(() =>
    {
        using (_lock.EnterScope())
        {
            return new Singleton();
        }
    });
    
    // 私有化无参构造函数
    private Singleton()
    {
        // 初始化逻辑
    }

    public static Singleton Instance => _lazyInstance.Value;
    // 其他方法实现
}

场景 3:带超时的缓存刷新

private readonly Lock _cacheLock = new();
private object _cachedData;

public object? GetCachedData()
{
    if (_cacheLock.TryEnter(TimeSpan.FromSeconds(1)))
    {
        try
        {
            if (_cachedData == null)
            {
                _cachedData = FetchFromDatabase();
            }
            return _cachedData;
        }
        finally
        {
            _cacheLock.Exit();
        }
    }
    else
    {
        // 超时处理逻辑
        return null;
    }
}

场景 4:避免跨线程访问 UI 控件(WinForms)

private readonly Lock _uiLock = new();

private void UpdateLabel(string text)
{
    using (_uiLock.EnterScope())
    {
        // 假设 label1 是 WinForm 上的控件
        if (label1.InvokeRequired)
        {
            label1.Invoke(new Action(() => label1.Text = text));
        }
        else
        {
            label1.Text = text;
        }
    }
}

⚠️ 注意事项

  • 不要跨线程传递 Lock.Scope 实例。
  • 避免在锁内部进行长时间操作,影响并发性能。
  • 优先使用 EnterScope() + using 来确保锁释放。
  • 若需更高性能读写分离,请考虑 ReaderWriterLockSlim

🧾 总结

特性 描述
类型 互斥锁(Mutex)
是否递归 是(默认)
是否公平 否(先进先出无法保证)
是否托管资源
推荐使用方式 EnterScope() + using

System.Threading.Lock.NET 9 中为现代化并发编程设计的新一代同步工具,相比传统的 lock(obj) 更加灵活可控,适合 高并发、异步、分布式 等复杂场景下的同步需求。