C# lock 线程锁 学习笔记(含 static / 非static、互斥/不互斥 全场景)
一、核心知识点
lock 实现互斥 的关键:多个线程必须争抢同一个锁对象。
- 锁对象为实例字段 :配合实例方法使用,不同实例锁相互独立。
- 锁对象为static 静态字段 :属于类本身,所有实例共享同一把锁。
- 互斥:同一锁对象,线程串行执行;
- 不互斥:不同锁对象 / 无 lock,线程并行执行。
二、场景1:方法 + 锁对象 均为 static(静态版)
2.1 代码(静态锁 → 全局互斥)
csharp
using System;
using System.Threading;
namespace LockDemo
{
class TextLock
{
// 静态锁对象:整个类全局唯一,所有线程共享
private static readonly object _lockObj = new object();
// 静态方法
static void SendCmd(string name)
{
lock (_lockObj)
{
Console.WriteLine($"[{name}] 开始 {DateTime.Now:HH:mm:ss.fff}");
Thread.Sleep(1000);
Console.WriteLine($"[{name}] 结束 {DateTime.Now:HH:mm:ss.fff}\n");
}
}
}
class Program
{
static void Main(string[] args)
{
// 静态方法:直接通过类名调用
Thread t1 = new Thread(() => TextLock.SendCmd("指令1"));
Thread t2 = new Thread(() => TextLock.SendCmd("指令2"));
t1.Start();
t2.Start();
Console.ReadKey();
}
}
}
2.2 运行效果
串行执行(完全互斥)
[指令1] 开始 10:20:00.100
[指令1] 结束 10:20:01.102
[指令2] 开始 10:20:01.103
[指令2] 结束 10:20:02.104
2.3 讲解
_lockObj被static修饰,属于类,不属于某个对象,全局只有一把锁。- 两个线程调用静态方法,争抢同一把静态锁,必然互斥。
- 特点:所有调用该方法的线程都会被限制串行执行,全局生效。
三、场景2:无 static(实例方法 + 实例锁)
3.1 子场景2.1:共用同一个实例 → 互斥
代码
csharp
using System;
using System.Threading;
namespace LockDemo
{
class TextLock
{
// 实例锁:每个对象独有一把锁
private readonly object _lockObj = new object();
// 实例方法(无 static)
public void SendCmd(string name)
{
lock (_lockObj)
{
Console.WriteLine($"[{name}] 开始 {DateTime.Now:HH:mm:ss.fff}");
Thread.Sleep(1000);
Console.WriteLine($"[{name}] 结束 {DateTime.Now:HH:mm:ss.fff}\n");
}
}
}
class Program
{
static void Main(string[] args)
{
// 只创建 1 个实例,两个线程共用它
TextLock lockDemo = new TextLock();
Thread t1 = new Thread(() => lockDemo.SendCmd("指令1"));
Thread t2 = new Thread(() => lockDemo.SendCmd("指令2"));
t1.Start();
t2.Start();
Console.ReadKey();
}
}
}
运行效果
串行执行(互斥)
[指令1] 开始 10:25:00.200
[指令1] 结束 10:25:01.201
[指令2] 开始 10:25:01.202
[指令2] 结束 10:25:02.203
讲解
- 锁和方法都是实例级别,依赖对象实例。
- 两个线程使用同一个对象实例,争抢同一把实例锁,实现互斥。
- 适用:多个线程操作同一个对象的共享资源。
3.2 子场景2.2:使用多个不同实例 → 不互斥
代码
csharp
using System;
using System.Threading;
namespace LockDemo
{
class TextLock
{
private readonly object _lockObj = new object();
public void SendCmd(string name)
{
lock (_lockObj)
{
Console.WriteLine($"[{name}] 开始 {DateTime.Now:HH:mm:ss.fff}");
Thread.Sleep(1000);
Console.WriteLine($"[{name}] 结束 {DateTime.Now:HH:mm:ss.fff}\n");
}
}
}
class Program
{
static void Main(string[] args)
{
// 创建 两个独立实例,各自拥有独立锁
TextLock obj1 = new TextLock();
TextLock obj2 = new TextLock();
Thread t1 = new Thread(() => obj1.SendCmd("指令1"));
Thread t2 = new Thread(() => obj2.SendCmd("指令2"));
t1.Start();
t2.Start();
Console.ReadKey();
}
}
}
运行效果
并行执行(不互斥)
[指令1] 开始 10:30:00.300
[指令2] 开始 10:30:00.301
[指令1] 结束 10:30:01.302
[指令2] 结束 10:30:01.303
讲解
- 每
new一个对象,都会生成全新独立的 _lockObj。 - 两个线程操作不同实例、不同锁对象,互不阻塞,并发运行。
- 适用:线程之间无共享资源,各自独立工作。
四、场景3:直接移除 lock → 无锁(必然不互斥)
不管是静态还是实例方法,去掉 lock 代码块,线程直接并发执行。
代码示例(实例方法无锁)
csharp
using System;
using System.Threading;
namespace LockDemo
{
class TextLock
{
// 无锁对象、无 lock 代码
public void SendCmd(string name)
{
Console.WriteLine($"[{name}] 开始 {DateTime.Now:HH:mm:ss.fff}");
Thread.Sleep(1000);
Console.WriteLine($"[{name}] 结束 {DateTime.Now:HH:mm:ss.fff}\n");
}
}
class Program
{
static void Main(string[] args)
{
TextLock demo = new TextLock();
Thread t1 = new Thread(() => demo.SendCmd("指令1"));
Thread t2 = new Thread(() => demo.SendCmd("指令2"));
t1.Start();
t2.Start();
Console.ReadKey();
}
}
}
效果与讲解
- 线程完全并行,不做任何互斥控制。
- 风险:若存在共享变量,会出现线程安全问题(数据错乱)。
五、四种场景汇总对比表
| 场景 | 修饰符 | 锁对象归属 | 执行方式 | 是否互斥 |
|---|---|---|---|---|
| 1 | 方法+锁 全 static | 类(全局唯一) | 串行 | ✅ 互斥 |
| 2 | 实例方法 + 单实例 | 对象实例(同一把锁) | 串行 | ✅ 互斥 |
| 3 | 实例方法 + 多实例 | 对象实例(多把独立锁) | 并行 | ❌ 不互斥 |
| 4 | 无 lock 关键字 | 无锁 | 并行 | ❌ 不互斥 |
六、总结要点
- static 锁:全局一把锁,所有调用都互斥,作用范围最大。
- 实例锁 :只对同一个对象生效,不同对象之间互不影响。
- 想要互斥:保证多线程使用同一个锁对象。
- 想要并发(不互斥):使用多个实例锁 或 直接去掉 lock。
- 有共享资源必须加锁保证线程安全;完全独立任务可不用锁提升效率。