lock 线程锁 学习笔记(含 static / 非static、互斥/不互斥 全场景)

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 讲解

  1. _lockObjstatic 修饰,属于类,不属于某个对象,全局只有一把锁。
  2. 两个线程调用静态方法,争抢同一把静态锁,必然互斥。
  3. 特点:所有调用该方法的线程都会被限制串行执行,全局生效。

三、场景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

讲解

  1. 锁和方法都是实例级别,依赖对象实例。
  2. 两个线程使用同一个对象实例,争抢同一把实例锁,实现互斥。
  3. 适用:多个线程操作同一个对象的共享资源

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

讲解

  1. new 一个对象,都会生成全新独立的 _lockObj
  2. 两个线程操作不同实例、不同锁对象,互不阻塞,并发运行。
  3. 适用:线程之间无共享资源,各自独立工作。

四、场景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 关键字 无锁 并行 ❌ 不互斥

六、总结要点

  1. static 锁:全局一把锁,所有调用都互斥,作用范围最大。
  2. 实例锁 :只对同一个对象生效,不同对象之间互不影响。
  3. 想要互斥:保证多线程使用同一个锁对象
  4. 想要并发(不互斥):使用多个实例锁直接去掉 lock
  5. 共享资源必须加锁保证线程安全;完全独立任务可不用锁提升效率。