字符串上的排除锁定具有奇怪的行为

本文关键字:排除 锁定 字符串 | 更新日期: 2023-09-27 17:56:33

我遇到了让我感到困惑的锁定语句问题:

如果将两个字符串与相同的表达式("1" + "2")连接起来,如下所示,lock语句将此表达式实现为字符串,并且锁定按预期工作:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        lock ("1" + "2")
        {//launched afetr 10 second
                        
        }
    });
    Thread.Sleep(10000);
}

但是,如果首先lock ("1" + "2")发生变化,var a="1"; lock (a + "2")

虽然两个表达式的结果相同,但 Lock 语句将其作为两个不同的表达式处理,因此第二个 Lock 语句立即启动:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched immediately
                        
        }
    });
    Thread.Sleep(10000);
}

请解释一下这种行为:

我知道在锁定语句 (MSDN) 中使用字符串违反了锁定准则。

字符串上的排除锁定具有奇怪的行为

如果代码更改为:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        lock ("12")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}
然后"内锁"

将在"外锁"后 10 秒打印。

这意味着"1"+"2"字面上等于"12"。

但是,如果使用 .NET 反射器打开以下各项的 IL 代码:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}

IL 将显示外部锁的以下代码

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] bool flag,
        [1] string str,
        [2] bool flag2)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: ldstr "12"
    L_0008: dup 
    L_0009: stloc.1 
    L_000a: ldloca.s flag
    L_000c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
    L_0011: nop 
    L_0012: nop 
    L_0013: ldstr "outer lock"
    L_0018: call void [mscorlib]System.Console::WriteLine(string)

IL 代码:

L_0003: ldstr "12"
L_0008: dup 
L_0009: stloc.1 
L_000a: ldloca.s flag

最终将"12"保存到本地变量中

"内部锁"的 IL 代码是

.method private hidebysig static void <Main>b__3() cil managed
{
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
    .maxstack 2
    .locals init (
        [0] string str,
        [1] bool flag,
        [2] string str2,
        [3] bool flag2)
    L_0000: nop 
    L_0001: ldstr "1"
    L_0006: stloc.0 
    L_0007: ldc.i4.0 
    L_0008: stloc.1 
    L_0009: ldloc.0 
    L_000a: ldstr "2"
    L_000f: call string [mscorlib]System.String::Concat(string, string)
    L_0014: dup 
    L_0015: stloc.2 
    L_0016: ldloca.s flag
    L_0018: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)

IL 代码:

L_0001: ldstr "1"
L_0006: stloc.0 
L_0007: ldc.i4.0 
L_0008: stloc.1 
L_0009: ldloc.0 
L_000a: ldstr "2"
L_000f: call string [mscorlib]System.String::Concat(string, string)
将"1"存储在一个局部变量中

,将"2"存储在另一个局部变量中。然后调用 String.Concat。

如果您尝试其他代码:(在另一个控制台程序中)

var c = "1" + "2";
var d = c + "2";
Console.WriteLine(string.IsInterned(d));
var e = "12";
Console.WriteLine(string.IsInterned(e));

您将找到第一个字符串。IsInterned(d) 不返回任何内容,只返回第二个字符串。IsInterned(e) 将在控制台中打印"12"。

因为c+"2"不是字面上等于"

1"+"2",而是"12"字面上等于"12"。

这意味着即使 c + "2" 也会返回 "12",但在内部它们是不同的表达式。这意味着您原来的第二个"锁定(a + "2")"正在尝试锁定不同的表达式,这就是为什么您的第二个代码块将立即执行的原因。