线程局部存储(TLS)

转自:http://blog.csdn.net/sundacheng1989/article/details/11647441

Java中有一种ThreadLocal机制,为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。比如在hibernate中使用Session的时候,因为Session是线程不安全的,所以要考虑并发问题。而使用ThreadLocal的话,会在每个线程中有一个Session的副本,所以就不会有线程冲突的问题。

.NET中也有相应的机制,来实现变量的线程局部化,而且有多种方法

1. 使用ThreadStatic特性

ThreadStatic特性是最简单的TLS使用,且只支持静态字段,只需要在字段上标记这个特性就可以了:

[csharp] view plain copy
 
  1. //TLS中的str变量  
  2. [ThreadStatic]  
  3. static string str = "hehe";  
  4.    
  5. static void Main()  
  6. {  
  7.     //另一个线程只会修改自己TLS中的str变量  
  8.     Thread th = new Thread(() => { str = "Mgen"; Display(); });  
  9.     th.Start();  
  10.     th.Join();  
  11.     Display();  
  12. }  
  13.    
  14. static void Display()  
  15. {  
  16.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, str);  
  17. }  

运行结果:

 

1 hehe
3 Mgen

 可以看到,str静态字段在两个线程中都是独立存储的,互相不会被修改。

 

2. 使用命名的LocalDataStoreSlot类型

显然ThreadStatic特性只支持静态字段太受限制了。.NET线程类型中的LocalDataStoreSlot提供更好的TLS支持。我们先来看看命名的LocalDataStoreSlot类型,可以通过Thread.AllocateNamedDataSlot来分配一个命名的空间,通过Thread.FreeNamedDataSlot来销毁一个命名的空间。空间数据的获取和设置则通过Thread类型的GetData方法和SetData方法。

 

[csharp] view plain copy
 
  1. static void Main()  
  2. {  
  3.     //创建Slot  
  4.     LocalDataStoreSlot slot = Thread.AllocateNamedDataSlot("slot");  
  5.     //设置TLS中的值  
  6.     Thread.SetData(slot, "hehe");  
  7.     //修改TLS的线程  
  8.     Thread th = new Thread(() =>  
  9.         {  
  10.             Thread.SetData(slot, "Mgen");  
  11.             Display();  
  12.         });  
  13.    
  14.     th.Start();  
  15.     th.Join();  
  16.     Display();  
  17.     //清除Slot  
  18.     Thread.FreeNamedDataSlot("slot");  
  19. }  
  20.    
  21. //显示TLS中Slot值  
  22. static void Display()  
  23. {  
  24.     LocalDataStoreSlot dataslot = Thread.GetNamedDataSlot("slot");  
  25.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(dataslot));  
  26. }  

输出:

 

3 Mgen
1 hehe


3. 使用未命名的LocalDataStoreSlot类型

线程同样支持未命名的LocalDataStoreSlot,未命名的LocalDataStoreSlot不需要手动清除,分配则需要Thread.AllocateDataSlot方法。注意由于未命名的LocalDataStoreSlot没有名称,因此无法使用Thread.GetNamedDataSlot方法,只能在多个线程中引用同一个LocalDataStoreSlot才可以对TLS空间进行操作,将上面的命名的LocalDataStoreSlot代码改成未命名的LocalDataStoreSlot执行:

[csharp] view plain copy
 
  1. //静态LocalDataStoreSlot变量  
  2. static LocalDataStoreSlot slot;  
  3.    
  4. static void Main()  
  5. {  
  6.     //创建Slot  
  7.     slot = Thread.AllocateDataSlot();  
  8.     //设置TLS中的值  
  9.     Thread.SetData(slot, "hehe");  
  10.     //修改TLS的线程  
  11.     Thread th = new Thread(() =>  
  12.         {  
  13.             Thread.SetData(slot, "Mgen");  
  14.             Display();  
  15.         });  
  16.    
  17.     th.Start();  
  18.     th.Join();  
  19.     Display();  
  20. }  
  21.    
  22. //显示TLS中Slot值  
  23. static void Display()  
  24. {  
  25.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(slot));  
  26. }  
  27.    


4. 使用.NET 4.0的ThreadLocal<T>类型

.NET 4.0在线程方面加入了很多东西,其中就包括ThreadLocal<T>类型,他的出现更大的简化了TLS的操作。ThreadLocal<T>类型和Lazy<T>惊人相似,构造函数参数是Func<T>用来创建对象(当然也可以理解成对象的默认值),然后用Value属性来得到或者设置这个对象。ThreadLocal的操作或多或少有点像上面的未命名的LocalDataStoreSlot,但ThreadLocal感觉更简洁更好理解。

[csharp] view plain copy
 
  1. static ThreadLocal<string> local;  
  2.    
  3. static void Main()  
  4. {  
  5.     //创建ThreadLocal并提供默认值  
  6.     local = new ThreadLocal<string>(() => "hehe");  
  7.     //修改TLS的线程  
  8.     Thread th = new Thread(() =>  
  9.         {  
  10.             local.Value = "Mgen";  
  11.             Display();  
  12.         });  
  13.    
  14.     th.Start();  
  15.     th.Join();  
  16.     Display();  
  17. }  
  18.    
  19. //显示TLS中数据值  
  20. static void Display()  
  21. {  
  22.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, local.Value);  
  23. }  


输出:

 

3 Mgen

1 hehe 

5. 强调一下不同方法和TLS的默认值
上面代码都是一个一个线程设置值,另一个线程直接修改值然后输出,不会觉察到TLS中默认值的状况,下面专门强调一下不同方法的默认值状况。ThreadStatic不提供默认值:

[csharp] view plain copy
 
  1. [ThreadStatic]  
  2. static int i = 123;  
  3.    
  4. static void Main()  
  5. {  
  6.     //输出本地线程TLS数据值  
  7.     Console.WriteLine(i);  
  8.     //输出另一个线程TLS数据值  
  9.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(i));  
  10.     //控制台等待线程结束  
  11.     Console.ReadKey();  
  12. }  

输出:
123

0

显然本地线程TLS数据时123,而静态变量的默认值不会在另一个线程中初始化的。

LocalDataStoreSlot很容易可以看出来,不可能有默认值,因为初始化只能构造一个空间,而不能赋予它值,Thread.SetData显然只会在TLS中设置数据,还是用代码演示一下:

[csharp] view plain copy
 
  1. static LocalDataStoreSlot slot = Thread.AllocateDataSlot();  
  2.    
  3. static void Main()  
  4. {  
  5.     Thread.SetData(slot, 123);  
  6.     //输出本地线程TLS数据值  
  7.     Console.WriteLine(Thread.GetData(slot));  
  8.     //输出另一个线程TLS数据值  
  9.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(Thread.GetData(slot) == null));  
  10.     //控制台等待线程结束  
  11.     Console.ReadKey();  
  12. }  


输出:
123
True
第二行是True,那么另一个线程中的数据是null。

最后重点:.NET 4.0后的ThreadLocal会提供默认值的,还记得我上面说的那句话“ThreadLocal的操作或多或少有点像上面的未命名的LocalDataStoreSlot”?有人可能会问那为什么要创造出ThreadLocal?还有一个很大的区别ThreadLocal可以提供TLS中数据的默认值。(另外还有ThreadLocal是泛型类,而LocalDataStoreSlot不是)。

[csharp] view plain copy
 
  1. static ThreadLocal<int> local = new ThreadLocal<int>(() => 123);  
  2. static void Main()  
  3. {  
  4.     //输出本地线程TLS数据值  
  5.     Console.WriteLine(local.Value);  
  6.     //输出另一个线程TLS数据值  
  7.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(local.Value));  
  8.     //控制台等待线程结束  
  9.     Console.ReadKey();  
  10. }  

输出:
123
123

Java中有一种ThreadLocal机制,为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。比如在hibernate中使用Session的时候,因为Session是线程不安全的,所以要考虑并发问题。而使用ThreadLocal的话,会在每个线程中有一个Session的副本,所以就不会有线程冲突的问题。

.NET中也有相应的机制,来实现变量的线程局部化,而且有多种方法

1. 使用ThreadStatic特性

ThreadStatic特性是最简单的TLS使用,且只支持静态字段,只需要在字段上标记这个特性就可以了:

[csharp] view plain copy
 
  1. //TLS中的str变量  
  2. [ThreadStatic]  
  3. static string str = "hehe";  
  4.    
  5. static void Main()  
  6. {  
  7.     //另一个线程只会修改自己TLS中的str变量  
  8.     Thread th = new Thread(() => { str = "Mgen"; Display(); });  
  9.     th.Start();  
  10.     th.Join();  
  11.     Display();  
  12. }  
  13.    
  14. static void Display()  
  15. {  
  16.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, str);  
  17. }  

运行结果:

 

1 hehe
3 Mgen

 可以看到,str静态字段在两个线程中都是独立存储的,互相不会被修改。

 

2. 使用命名的LocalDataStoreSlot类型

显然ThreadStatic特性只支持静态字段太受限制了。.NET线程类型中的LocalDataStoreSlot提供更好的TLS支持。我们先来看看命名的LocalDataStoreSlot类型,可以通过Thread.AllocateNamedDataSlot来分配一个命名的空间,通过Thread.FreeNamedDataSlot来销毁一个命名的空间。空间数据的获取和设置则通过Thread类型的GetData方法和SetData方法。

 

[csharp] view plain copy
 
  1. static void Main()  
  2. {  
  3.     //创建Slot  
  4.     LocalDataStoreSlot slot = Thread.AllocateNamedDataSlot("slot");  
  5.     //设置TLS中的值  
  6.     Thread.SetData(slot, "hehe");  
  7.     //修改TLS的线程  
  8.     Thread th = new Thread(() =>  
  9.         {  
  10.             Thread.SetData(slot, "Mgen");  
  11.             Display();  
  12.         });  
  13.    
  14.     th.Start();  
  15.     th.Join();  
  16.     Display();  
  17.     //清除Slot  
  18.     Thread.FreeNamedDataSlot("slot");  
  19. }  
  20.    
  21. //显示TLS中Slot值  
  22. static void Display()  
  23. {  
  24.     LocalDataStoreSlot dataslot = Thread.GetNamedDataSlot("slot");  
  25.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(dataslot));  
  26. }  

输出:

 

3 Mgen
1 hehe


3. 使用未命名的LocalDataStoreSlot类型

线程同样支持未命名的LocalDataStoreSlot,未命名的LocalDataStoreSlot不需要手动清除,分配则需要Thread.AllocateDataSlot方法。注意由于未命名的LocalDataStoreSlot没有名称,因此无法使用Thread.GetNamedDataSlot方法,只能在多个线程中引用同一个LocalDataStoreSlot才可以对TLS空间进行操作,将上面的命名的LocalDataStoreSlot代码改成未命名的LocalDataStoreSlot执行:

[csharp] view plain copy
 
  1. //静态LocalDataStoreSlot变量  
  2. static LocalDataStoreSlot slot;  
  3.    
  4. static void Main()  
  5. {  
  6.     //创建Slot  
  7.     slot = Thread.AllocateDataSlot();  
  8.     //设置TLS中的值  
  9.     Thread.SetData(slot, "hehe");  
  10.     //修改TLS的线程  
  11.     Thread th = new Thread(() =>  
  12.         {  
  13.             Thread.SetData(slot, "Mgen");  
  14.             Display();  
  15.         });  
  16.    
  17.     th.Start();  
  18.     th.Join();  
  19.     Display();  
  20. }  
  21.    
  22. //显示TLS中Slot值  
  23. static void Display()  
  24. {  
  25.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, Thread.GetData(slot));  
  26. }  
  27.    


4. 使用.NET 4.0的ThreadLocal<T>类型

.NET 4.0在线程方面加入了很多东西,其中就包括ThreadLocal<T>类型,他的出现更大的简化了TLS的操作。ThreadLocal<T>类型和Lazy<T>惊人相似,构造函数参数是Func<T>用来创建对象(当然也可以理解成对象的默认值),然后用Value属性来得到或者设置这个对象。ThreadLocal的操作或多或少有点像上面的未命名的LocalDataStoreSlot,但ThreadLocal感觉更简洁更好理解。

[csharp] view plain copy
 
  1. static ThreadLocal<string> local;  
  2.    
  3. static void Main()  
  4. {  
  5.     //创建ThreadLocal并提供默认值  
  6.     local = new ThreadLocal<string>(() => "hehe");  
  7.     //修改TLS的线程  
  8.     Thread th = new Thread(() =>  
  9.         {  
  10.             local.Value = "Mgen";  
  11.             Display();  
  12.         });  
  13.    
  14.     th.Start();  
  15.     th.Join();  
  16.     Display();  
  17. }  
  18.    
  19. //显示TLS中数据值  
  20. static void Display()  
  21. {  
  22.     Console.WriteLine("{0} {1}", Thread.CurrentThread.ManagedThreadId, local.Value);  
  23. }  


输出:

 

3 Mgen

1 hehe 

5. 强调一下不同方法和TLS的默认值
上面代码都是一个一个线程设置值,另一个线程直接修改值然后输出,不会觉察到TLS中默认值的状况,下面专门强调一下不同方法的默认值状况。ThreadStatic不提供默认值:

[csharp] view plain copy
 
  1. [ThreadStatic]  
  2. static int i = 123;  
  3.    
  4. static void Main()  
  5. {  
  6.     //输出本地线程TLS数据值  
  7.     Console.WriteLine(i);  
  8.     //输出另一个线程TLS数据值  
  9.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(i));  
  10.     //控制台等待线程结束  
  11.     Console.ReadKey();  
  12. }  

输出:
123

0

显然本地线程TLS数据时123,而静态变量的默认值不会在另一个线程中初始化的。

LocalDataStoreSlot很容易可以看出来,不可能有默认值,因为初始化只能构造一个空间,而不能赋予它值,Thread.SetData显然只会在TLS中设置数据,还是用代码演示一下:

[csharp] view plain copy
 
  1. static LocalDataStoreSlot slot = Thread.AllocateDataSlot();  
  2.    
  3. static void Main()  
  4. {  
  5.     Thread.SetData(slot, 123);  
  6.     //输出本地线程TLS数据值  
  7.     Console.WriteLine(Thread.GetData(slot));  
  8.     //输出另一个线程TLS数据值  
  9.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(Thread.GetData(slot) == null));  
  10.     //控制台等待线程结束  
  11.     Console.ReadKey();  
  12. }  


输出:
123
True
第二行是True,那么另一个线程中的数据是null。

最后重点:.NET 4.0后的ThreadLocal会提供默认值的,还记得我上面说的那句话“ThreadLocal的操作或多或少有点像上面的未命名的LocalDataStoreSlot”?有人可能会问那为什么要创造出ThreadLocal?还有一个很大的区别ThreadLocal可以提供TLS中数据的默认值。(另外还有ThreadLocal是泛型类,而LocalDataStoreSlot不是)。

[csharp] view plain copy
 
  1. static ThreadLocal<int> local = new ThreadLocal<int>(() => 123);  
  2. static void Main()  
  3. {  
  4.     //输出本地线程TLS数据值  
  5.     Console.WriteLine(local.Value);  
  6.     //输出另一个线程TLS数据值  
  7.     ThreadPool.QueueUserWorkItem(_ => Console.WriteLine(local.Value));  
  8.     //控制台等待线程结束  
  9.     Console.ReadKey();  
  10. }  

输出:
123
123

posted @ 2017-08-25 13:19  sunlyk  阅读(434)  评论(0编辑  收藏  举报