線程局部存儲(TLS)

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

Java中有一種ThreadLocal機制,爲每個使用該變量的線程都提供一個變量值的副本,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。從線程的角度看,就好像每個線程都徹底擁有該變量。好比在hibernate中使用Session的時候,由於Session是線程不安全的,因此要考慮併發問題。而使用ThreadLocal的話,會在每一個線程中有一個Session的副本,因此就不會有線程衝突的問題。java

.NET中也有相應的機制,來實現變量的線程局部化,並且有多種方法安全

1. 使用ThreadStatic特性併發

ThreadStatic特性是最簡單的TLS使用,且只支持靜態字段,只須要在字段上標記這個特性就能夠了:ide

[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 Mgenurl

 能夠看到,str靜態字段在兩個線程中都是獨立存儲的,互相不會被修改。spa

 

2. 使用命名的LocalDataStoreSlot類型.net

顯然ThreadStatic特性只支持靜態字段太受限制了。.NET線程類型中的LocalDataStoreSlot提供更好的TLS支持。咱們先來看看命名的LocalDataStoreSlot類型,能夠經過Thread.AllocateNamedDataSlot來分配一個命名的空間,經過Thread.FreeNamedDataSlot來銷燬一個命名的空間。空間數據的獲取和設置則經過Thread類型的GetData方法和SetData方法。hibernate

 

[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

相關文章
相關標籤/搜索