C# lock string 字符串 "暂留"

class  TestWorker
 2  {        
 3       public   void  DoMultiThreadedWork( object  someParameter)
 4      {
 5           lock  (lockObject)
 6          {
 7               //   lots of work 
 8          }
 9      }
10 
11       private   string  lockObject  =   " lockit " ;
12 

这段代码很简单,大家看看这段代码有什么问题呢?

 

 

开始这么一看似乎没什么大的问题。可是仔细分析一下代码你就可以知道其中还是有一些很严重的问题的。假如你对Lock机制有所了解并且对string类型有过研究的话你就会发现出问题:

lock 关键字的参数必须为基于引用类型的对象,该对象用来定义锁的范围。在上例中,锁的范围限定为此函数,因为函数外不存在任何对该对象的引用。严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。然而,实际上,此对象通常表示需要进行线程同步的资源。例如,如果一个容器对象将被多个线程使用,则可以将该容器传递给 lock,而 lock 后面的同步代码块将访问该容器。只要其他线程在访问该容器前先锁定该容器,则对该对象的访问将是安全同步的。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。关于Lock被编译器编译过后的代码请参考我的另一篇文章:Inside C#

StringCLR中有两个重要的属性:不变性和字符串驻留。这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。

所以锁定字符串尤其危险。

下面我们来看看字符串驻留是个怎样的东东。看看下面的代码

 1                string  a  =   " lockit " ;  
 2                string  b  =   " lockit "
 3                string  c  =   " LOCKIT " .ToLower();  
 4                string  d  =   " lock "   +   " it "
 5                string  e1  =   " lock " ;
 6                string  e2  =   " it " ;
 7                string  ee  =  e1  +   " it " ;
 8                string  e  =  e1  +  e2; 
 9               Console.WriteLine( object .ReferenceEquals(a, b));
10               Console.WriteLine( object .ReferenceEquals(a, c));
11               Console.WriteLine( object .ReferenceEquals(a, d));
12               Console.WriteLine( object .ReferenceEquals(a, ee));
13               Console.WriteLine( object .ReferenceEquals(a, e));

 下边是输出的结果:

True,False,True,False,False

我们知道CLR处理每个对象在内存的时候都会额外的生成一个syncblockindex空间,这个就是用于对象同步用的。是大家平时开发使用最多的一个类型,MS的CLR部门为了简化操作和性能的优化做了两点处理,一是将string的创建过程简单化。一般的对象在创建的时候通过new关键字来实现;而string不需要这么做,我们只需要把对应的字符换赋给给对应的字符串变量就可以了。那么他们在创建过程中使用的MSIL指令时不同的——一般的引用对象的创建是通过newobj这样一个IL指令来实现的,而创建一个字符串变量的IL指令则是ldstrload string)。其次就是为了考虑性能的提升和内存节约上,对于创建相同的字符串,一般不会为他们分别分配内存块,相反地,他们会共享一块内存。CLR内部维护一个HashTable,这HashTable维护者大部分创建的string。这个HashTableKey对应的相应的string本身,而Value则是分配给这个string的内存块的引用。我们知道在一个托管进程被创建以后,在托管进程的内存空间里面,包含了System DomainShared Domain等等,而这个HashTable就是放在System Domain里,所以它是在这整个程序的生命周期里都是存在的和被共享的。一般地,在程序运行过程中,如果需要的创建一个stringCLR会根据这个stringHash Code试着在HashTable中找这个相同的string,如果找到,则直接把找到的string的地址赋给相应的变量,如果没有则在托管堆中创建一个stringCLR会先在managed heap中创建该strng,并在HashTable中创建一个Key-Value,Key为这个string本身,Valuestring的内存地址,这个地址最重被赋给响应的变量。

上面的例子后面两个为False告诉我们对于对一个动态创建的字符串,驻留机制便不会起作用。这种情况产生的MSIL也不一样。但是我们可以用System.String中的静态方法Intern来解决。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C#中,可以使用"+"运算符或者String.Concat方法来将字符串追加到另一个字符串的末尾。另外,还可以使用StringBuilder类来高效地进行字符串的追加操作。 以下是使用"+"运算符和String.Concat方法的示例代码: ```csharp string str1 = "Hello"; string str2 = "World"; string result1 = str1 + str2; // 使用"+"运算符 string result2 = String.Concat(str1, str2); // 使用String.Concat方法 ``` 另外,使用StringBuilder类进行字符串的追加操作可以提高性能,特别是在需要多次追加字符串的情况下。以下是使用StringBuilder类的示例代码: ```csharp StringBuilder sb = new StringBuilder(); sb.Append("Hello"); sb.Append("World"); string result = sb.ToString(); ``` 在上述示例中,首先创建了一个StringBuilder对象,然后使用Append方法多次追加字符串,最后使用ToString方法将StringBuilder对象转换为最终的字符串。 请注意,由于字符串的不可变性,每次对字符串进行追加操作时都会创建一个新的字符串对象。因此,在需要频繁进行字符串追加操作时,建议使用StringBuilder类以提高性能。 #### 引用[.reference_title] - *1* *2* *3* [C#字符串](https://blog.csdn.net/weixin_40960364/article/details/112259370)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值