当多个线程可以调用单个对象的属性和方法时,必须同步这些调用。 否则,一个线程可能会中断另一个线程正在执行的操作,并且该对象可能处于无效状态。 其成员不受此类中断保护的类称为线程安全。
.NET提供了多个策略来同步对实例和静态成员的访问:
同步的代码区域。 可以使用 Monitor 类,或使用编译器对此类的支持,仅同步需要同步的代码块,从而提高性能。
同步上下文。 仅对于 .NET Framework 应用程序,可以使用 SynchronizationAttribute 为 ContextBoundObject 对象启用简单的自动同步。
命名空间中的 System.Collections.Concurrent 集合类。 这些类提供内置同步的添加和删除操作。 有关详细信息,请参阅 Thread-Safe 集合。
公共语言运行时提供了一个线程模型,其中类属于许多类别,这些类别可以根据要求以各种不同方式同步。 下表显示了为具有给定同步类别的字段和方法提供哪些同步支持。
| Category | 全局字段 | 静态字段 | 静态方法 | 实例字段 | 实例方法 | 特定代码块 |
|---|---|---|---|---|---|---|
| 无同步 | 否 | 否 | 否 | 否 | 否 | 否 |
| 同步上下文 | 否 | 否 | 否 | 是的 | 是的 | 否 |
| 同步的代码区域 | 否 | 否 | 仅当标记为 | 否 | 仅当标记时 | 仅当已标记时 |
| 手动同步 | Manual | Manual | Manual | Manual | Manual | Manual |
无同步
这是对象的默认值。 任何线程随时都可以访问任何方法或字段。 一次只能有一个线程访问这些对象。
手动同步
.NET类库提供了许多用于同步线程的类。 请参阅 同步基元概述。
同步的代码区域
可以使用 Monitor 类或编译器关键字来同步代码块、实例方法和静态方法。 不支持同步的静态字段。
Visual Basic和 C# 都支持使用特定语言关键字、lockC# 中的语句或SyncLockVisual Basic中的语句标记代码块。 当代码由线程执行时,将尝试获取锁。 如果锁已被另一个线程获取,则线程会阻塞,直到锁变为可用为止。 当线程退出同步的代码块时,无论线程如何退出块,该锁都释放。
Note
在 .NET 9 和 C# 13 或更高版本中,如果lock目标是专用System.Threading.Lock实例,则lock语句用于Lock.EnterScope()创建同步区域。
Visual Basic 不支持在SyncLock中使用System.Threading.Lock,因此请继续将SyncLock与专用的私有引用类型配合使用。
如果 lock 目标不是 Lock 实例,并且对于所有 SyncLock 的用法,编译器使用 Monitor.Enter 和 Monitor.Exit。 由于该实现,你仍然可以在同步区域中使用其他 Monitor 方法。
还可以用值为 MethodImplOptions.Synchronized 的 MethodImplAttribute 修饰方法,其效果与使用 Monitor 或某个编译器关键字来锁定整个方法体相同。
Thread.Interrupt 可用于将线程从阻塞操作中中断,例如等待访问同步的代码区域。 Thread.Interrupt 还用于将线程从操作中分解出来,例如 Thread.Sleep。
Important
不要对类型加锁——也就是说,不要对 C# 中的 typeof(MyType)、Visual Basic 中的 GetType(MyType) 或 C++ 中的 MyType::typeid 加锁——以保护 static 方法(在 Visual Basic 中为 Shared 方法)。 请改用专用静态对象。 同样,不要在 C# 中使用 this(在 Visual Basic 中为 Me)来锁定实例方法。 请改用专用对象。 类或实例可能会被你自己的代码以外的代码锁定,从而导致死锁或性能问题。
编译器支持
Visual Basic和 C# 都支持使用Monitor.Enter和Monitor.Exit锁定对象的语言关键字。 Visual Basic支持 SyncLock 语句;C# 支持 lock 语句。
在这两种情况下,如果代码块中引发了异常,则由 lock 或 SyncLock 获取的锁会自动释放。 C# 和 Visual Basic 编译器会生成一个 try/finally 块,其中在 try 开始处调用 Monitor.Enter,并在 finally 块中调用 Monitor.Exit。 如果 lock 或 SyncLock 块内部引发了异常,则会运行 finally 处理程序,从而允许执行任何清除工作。
同步上下文
仅在 .NET Framework 应用程序中,可以在任何 ContextBoundObject 上使用 SynchronizationAttribute 来同步所有实例方法和字段。 同一上下文域中的所有对象共享相同的锁。 允许多个线程访问方法和字段,但每次只允许一个线程。