同样的执行代码,放在单线程中执行,和放在多线程中执行,结果不一样,这就是多线程安全问题。
线程安全问题是怎么来的?
一个语法糖:

public static void TestMonitor() { string LOCK= "lock"; List<int> list = new List<int>(); for (int i = 0; i < 10000; i++) { Task.Run(() => { Monitor.Enter(LOCK); try { list.Add(i); } finally { Monitor.Exit(LOCK); } }); } Thread.Sleep(10000); Console.WriteLine("listCount:"+list.Count()); Console.ReadLine(); }
下面通过几个具体的实例,来讲解一下lock使用中的一些常见的问题。
案例1:
下面还有一个使用场景是这样,不同的实例间,锁住的同一个非静态变量的话,相同实例下的调用是不会并发运行,不同实例间是会并发执行的。
public class MyLock { public readonly object OneLock=new object(); public void LockCase(string caceName,int count) { for (int i = 0; i < count; i++) { string name = caceName; Task.Run(() => { lock (OneLock) { Console.WriteLine($"{name},Start"); Thread.Sleep(700); Console.WriteLine($"{name},Finish"); } }); } } }
static void Main(string[] args) { MyLock myLock1 = new MyLock(); MyLock myLock2 = new MyLock(); myLock1.LockCase("case1", 10); myLock2.LockCase("case3",10); myLock1.LockCase("case4", 20); myLock2.LockCase("case2",15); Console.ReadLine(); }

实际运行中,case1和case4是一个实例,case2和case3是一个实例,如上边描述,实际情况也是,case1和case4不会同时运行,因为他们lock的是同一个资源;同理case2和case3也是。但是case1和case2/case3不是同一实例,那么case1与case2/case3可以同时运行,因为他们锁的不是同一资源。
但是,这个仅仅限于public readonly object OneLock=new object(); 这种声明,假如public static readonly object OneLock=new object();也就是资源为静态资源的时候,那么在Main()方法中的不通实例间,其实也是相当于同一资源,即全局唯一,因此不同实例间也不会同时运行。小结一下就是,不同实例的非静态字段,是不同的;同一实例的非静态字段是相同的。如果是静态字段,那么不同实例间字段也是相同的,因为全局唯一。
案例2:
private readonly string StringLock = "StringLock"; public void StringLockCase(string caceName, int count) { for (int i = 0; i < count; i++) { string name = caceName; Task.Run(() => { lock (StringLock) { Console.WriteLine($"{name},Start"); Thread.Sleep(700); Console.WriteLine($"{name},Finish"); } }); } }

这里边有一个知识点,就是string变量在栈中的存储特征有一点特殊,享元模式。(但是如果存在字符串拼接的话,就不在遵循享元模式)
在c#中,String的存储方式很特殊,在c#的内存中,在常量区里会分配一块空间叫做String暂存池(常量池),在某些时候,我们的字符串数据是存储在这个常量池中的,而地址依然是存放在栈中。
例如用 String str = "xXXXX" 的方式来创建String变量的话,那么String的值便会存储在String常量池中,在我们以这种方式创建String变量时,编译器会先判断你这个内容有没有已经在常量池出现过了,如果已经出现过,那么不会再在常量池中使用空间来存放一个相同的内容,这个内容只会固定有一个引用,所以在创造相同内容的String的时候,他们的引用都是相同的。又有一种情况:一开始A和B内容相同,就是说A与B的引用都相同时,此时将B的内容更改,那么B的内容在常量池中就会使用另一块空间,那么相应的B的引用也会改变,而A的引用并不会改变,因为A此时还是存储的原来的内容。我们可以来看简易的图解:

回到案例中,case1与 case2/case3虽然是不同实例,当然包括base4,但是在调用过程中,已经存在了“StringLock”,那么相当于锁住的是同一个资源。当我们把lock的资源换成其他类型,就不会出现这种问题了,如下图:

所以,使用lock时,尽量不要使用string,避免出现不可控的意外情况。
案例3:
public class MyLockGeneric<T> { private static readonly object MyLock = new object(); public static void LockCase(string caceName, int count) { for (int i = 0; i < count; i++) { string name = caceName; Task.Run(() => { lock (MyLock) { Console.WriteLine($"{name},Start"); Thread.Sleep(700); Console.WriteLine($"{name},Finish"); } }); } } }

泛型的特点,声明的时候不知道具体类型,只是用T作为占位符,调用的时候才知道具体类型,诞生了一个新的类,具体类型来替代T,不同的具体类型,产生的新的类时不同的,不通的类型里面的静态字段当然也不相同,当然,同一种泛型类型,比如都是int,那就是同一个类,也是同一个静态字段。
回到示例中,case1和case2因为T相同,那么实例化之后应该时同一个类,那么静态字段全局唯一。所以case1和case2不会并发执行。
case1/case2 与case3或case4 T不是相同类型,那么实例化之后为不通的类,那么静态字段,在本类型内全局唯一。那么case1/case2 与case3或case4是可以并发执行的。
案例4:
public void LockThis(string caceName, int count) { for (int i = 0; i < count; i++) { string name = caceName; Task.Run(() => { lock (this) { Console.WriteLine($"{name},Start"); Thread.Sleep(700); Console.WriteLine($"{name},Finish"); } }); } }
static void Main(string[] args) { MyLock myLock1 = new MyLock(); MyLock myLock2 = new MyLock(); myLock1.LockThis("case1", 5); myLock2.LockThis("case2", 5); myLock2.LockThis("case3", 5); string name = "case4"; for (int i = 0; i < 5; i++) { Task.Run(() => { lock (myLock1) { Console.WriteLine($"{name},Start"); Thread.Sleep(700); Console.WriteLine($"{name},Finish"); } }); } Console.ReadLine(); }

如案例中所示,case2和case3为同一实例,那么case2和case3是不会并发运行的。case4锁住的是myLock1这个实例,case1中锁住的是this,其实也是myLock1那么其实case1与case4是不可能并发运行的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器