程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 多線程下的集合安全,多線程集合安全

多線程下的集合安全,多線程集合安全

編輯:C#入門知識

多線程下的集合安全,多線程集合安全


        在多線程內使用集合,如果未對集合做任何安全處理,就非常容易出現系統崩潰或各種錯誤。最近的項目裡,使用的是socket通信後再改變了某個集合,結果導致系統直接崩潰,且無任何錯誤系統彈出。

         經排查,發現問題是執行某集合後,系統就會在一定時間內退出,最後發現是使用的一個字典集合出了問題。稍微思考後,就認定了是線程安全問題。因為此集合在其它幾個地方都有線程做循環讀取。

          下面是我模擬的一個示例,沒有進行任何的安全處理:

 1 class Program
 2     {
 3         static MyCollection mycoll;
 4         static void Main(string[] args)
 5         {
 6             mycoll = new MyCollection();
 7             Thread readT = new Thread(new ThreadStart(ReadMethod));
 8             readT.Start();
 9 
10             Thread addT = new Thread(new ThreadStart(AddMethod));
11             addT.Start();
12             Console.ReadLine();
13         }
14         public static void AddMethod()
15         {
16             for(int i=0;i<10;i++)
17             {
18                 Thread.Sleep(500);
19                 mycoll.Add("a"+i, i);
20             }
21         }
22         public static void ReadMethod()
23         {
24             while (true)
25             {
26                 Thread.Sleep(100);
27                 foreach (KeyValuePair<string, int> item in mycoll.myDic)
28                 {
29                     Console.WriteLine(item.Key + "\\t" + item.Value);
30                     //其它處理
31                     Thread.Sleep(2000);
32                 }
33             }
34         }
35     }
36     public class MyCollection
37     {
38         public Dictionary<string, int> myDic = new Dictionary<string, int>();
39 
40         public void Add(string key, int value)
41         {
42             if (myDic.ContainsKey(key))
43             {
44                 myDic[key] += 1;
45             }
46             else
47             {
48                 myDic.Add(key, value);
49             }
50         }
51 
52         public void Remove(string key)
53         {
54             if (myDic.ContainsKey(key))
55             {
56                 myDic.Remove(key);
57             }
58         }
59     }

在上面的示例中,創建了一個Dictionary字典對像,程序運行時,輸出了下面的錯誤:

程序運行時,輸出了上面的錯誤,僅僅輸出了一行結果

這次測試有明顯示的錯誤提示,集合已修改;可能無法執行枚舉操作。

唉,真是一個常見的問題,在foreach的時侯又修改集合,就一定會出現問題了,因為foreach是只讀的,在進行遍歷時不可以對集合進行任何修改。

看到這裡,我們會想到,如果使用for循環進行逆向獲取,也許可以解決此問題。

非常可惜,字典對像沒有使用索引號獲取的辦法,下面的表格轉自(http://www.cnblogs.com/yang_sy/p/3678905.html)

Type 內部結構 支持索引 內存占用 隨機插入的速度(毫秒) 順序插入的速度(毫秒) 根據鍵獲取元素的速度(毫秒) 未排序字典             Dictionary<T,V> 哈希表 否 22 30 30 20 Hashtable 哈希表 否 38 50 50 30 ListDictionary 鏈表 否 36 50000 50000 50000 OrderedDictionary 哈希表 +數組 是 59 70 70 40 排序字典             SortedDictionary<K,V> 紅黑樹 否 20 130 100 120 SortedList<K,V> 2xArray 是 20 3300 30 40 SortList 2xArray 是 27 4500 100 180

從時間復雜度來講,從字典中通過鍵獲取值所耗費的時間分別如下:

  • Hashtable, Dictionary和OrderedDictionary的時間復雜度為O(1)
  • SortedDictionary和SortList的時間復雜度為O(logN)
  • ListDictinary的時間復雜度為O(n)

這可如何是好,只能改為可排序的對像?然後使用for解決?

我突然想到,是否可以在循環時縮短foreach,來解決此問題呢?

想到可以在循環時先copy一份副本,然後再進行循環操作,編寫代碼,查找copy的方法。真是無奈,沒有提供任何的copy方法。唉!看來人都是用來被逼的,先改個對象吧:

把Dictionary修改成了Hashtable對像(也沒有索引排序)。代碼如下:

 1  class Program
 2     {
 3         static MyCollection mycoll;
 4         static void Main(string[] args)
 5         {
 6             mycoll = new MyCollection();
 7             Thread readT = new Thread(new ThreadStart(ReadMethod));
 8             readT.Start();
 9 
10             Thread addT = new Thread(new ThreadStart(AddMethod));
11             addT.Start();
12             Console.ReadLine();
13         }
14         public static void AddMethod()
15         {
16             for(int i=0;i<10;i++)
17             {
18                 Thread.Sleep(500);
19                 mycoll.Add("a"+i, i);
20             }
21         }
22         public static void ReadMethod()
23         {
24             while (true)
25             {
26                 Thread.Sleep(100);
27                 foreach (DictionaryEntry item in mycoll.myDic)
28                 {
29                     Console.WriteLine(item.Key + "      " + item.Value);
30                     //其它處理
31                     Thread.Sleep(2000);
32                 }
33             }
34         }
35     }
36     public class MyCollection
37     {
38         public Hashtable myDic = new Hashtable();
39         
40         public void Add(string key, int value)
41         {
42             if (myDic.ContainsKey(key))
43             {
44                 
45                 myDic[key] =Convert.ToInt32(myDic[key])+ 1;
46             }
47             else
48             {
49                 myDic.Add(key, value);
50             }
51         }
52 
53         public void Remove(string key)
54         {
55             if (myDic.ContainsKey(key))
56             {
57                 myDic.Remove(key);
58             }
59         }
60     }

代碼一如即往的報錯,錯誤信息一樣。
使用copy法試試

 1 class Program
 2     {
 3         static MyCollection mycoll;
 4         static void Main(string[] args)
 5         {
 6             mycoll = new MyCollection();
 7             Thread readT = new Thread(new ThreadStart(ReadMethod));
 8             readT.Start();
 9 
10             Thread addT = new Thread(new ThreadStart(AddMethod));
11             addT.Start();
12             Console.ReadLine();
13         }
14         public static void AddMethod()
15         {
16             for(int i=0;i<10;i++)
17             {
18                 Thread.Sleep(500);
19                 mycoll.Add("a"+i, i);
20             }
21         }
22         public static void ReadMethod()
23         {
24             Hashtable tempHt = null;
25             while (true)
26             {
27                 Thread.Sleep(100);
28                 tempHt = mycoll.myDic.Clone() as Hashtable;
29                 Console.WriteLine("\r\n=================================\r\n");
30                 foreach (DictionaryEntry item in tempHt)
31                 {
32                     Console.WriteLine(item.Key + "      " + item.Value);
33                     //其它處理
34                     Thread.Sleep(2000);
35                 }
36             }
37         }
38     }
39     public class MyCollection
40     {
41         public Hashtable myDic = new Hashtable();
42         
43         public void Add(string key, int value)
44         {
45             if (myDic.ContainsKey(key))
46             {
47                 
48                 myDic[key] =Convert.ToInt32(myDic[key])+ 1;
49             }
50             else
51             {
52                 myDic.Add(key, value);
53             }
54         }
55 
56         public void Remove(string key)
57         {
58             if (myDic.ContainsKey(key))
59             {
60                 myDic.Remove(key);
61             }
62         }
63     }

輸出結果如下:

以上結果輸出

寫到這裡,我自己都有些模糊了。這文章和線程安全有毛關系。

根據msdn線程安全解釋如下:


線程安全  

Hashtable 是線程安全的,可由多個讀取器線程或一個寫入線程使用。多線程使用時,如果任何一個線程執行寫入(更新)操作,它都不是線程安全的。若要支持多個編寫器,如果沒有任何線程在讀取 Hashtable 對象,則對 Hashtable 的所有操作都必須通過 Synchronized 方法返回的包裝完成。

從頭到尾對一個集合進行枚舉本質上並不是一個線程安全的過程。即使一個集合已進行同步,其他線程仍可以修改該集合,這將導致枚舉數引發異常。若要在枚舉過程中保證線程安全,可以在整個枚舉過程中鎖定集合,或者捕捉由於其他線程進行的更改而引發的異常。

經過我們模擬,沒有發現多線程下錯誤,但為安全起見,我們在使用時,最好根據msdn所述,在對線程操作時加上安全鎖處理,這裡我們不需自己定義鎖對象,因為微軟直接提供了SyncRoot進行安全鎖處理。 修改後的代碼如下:
  1 class Program
  2     {
  3         static MyCollection mycoll;
  4         static void Main(string[] args)
  5         {
  6             mycoll = new MyCollection();
  7             Thread readT = new Thread(new ThreadStart(ReadMethod));
  8             readT.Start();
  9 
 10             Thread addT = new Thread(new ThreadStart(AddMethod));
 11             addT.Start();
 12 
 13 
 14             Thread addT2 = new Thread(new ThreadStart(AddMethod2));
 15             addT2.Start();
 16 
 17             Thread delT = new Thread(new ThreadStart(DelMethod));
 18             delT.Start();
 19 
 20             Thread delT2 = new Thread(new ThreadStart(DelMethod2));
 21             delT2.Start();
 22 
 23             Console.ReadLine();
 24         }
 25 
 26         public static void DelMethod()
 27         {
 28             for (int i = 0; i < 10; i++)
 29             {
 30                 Thread.Sleep(800);
 31                 if(mycoll.myDic.ContainsKey("a"+i))
 32                 mycoll.myDic.Remove("a" + i);
 33             }
 34         }
 35 
 36         public static void DelMethod2()
 37         {
 38             for (int i = 0; i < 10; i++)
 39             {
 40                 Thread.Sleep(800);
 41                 if (mycoll.myDic.ContainsKey("b" + i))
 42                     mycoll.myDic.Remove("b" + i);
 43             }
 44         }
 45 
 46         public static void AddMethod2()
 47         {
 48             for (int i = 0; i < 10; i++)
 49             {
 50                 Thread.Sleep(500);
 51                 mycoll.Add("b" + i, i);
 52             }
 53         }
 54         public static void AddMethod()
 55         {
 56             for(int i=0;i<10;i++)
 57             {
 58                 Thread.Sleep(500);
 59                 mycoll.Add("a"+i, i);
 60             }
 61         }
 62         public static void ReadMethod()
 63         {
 64             Hashtable tempHt = null;
 65             while (true)
 66             {
 67                 Thread.Sleep(100);
 68                 lock (mycoll.myDic.SyncRoot)
 69                 {
 70                     tempHt = mycoll.myDic.Clone() as Hashtable;
 71                 }
 72                 Console.WriteLine("\r\n=================================\r\n");
 73                 foreach (DictionaryEntry item in tempHt)
 74                 {
 75                     Console.WriteLine(item.Key + "      " + item.Value);
 76                     //其它處理
 77                     Thread.Sleep(600);
 78                 }
 79             }
 80         }
 81     }
 82     public class MyCollection
 83     {
 84         public Hashtable myDic = new Hashtable();
 85         
 86         public void Add(string key, int value)
 87         {
 88             lock (myDic.SyncRoot)
 89             {
 90                 if (myDic.ContainsKey(key))
 91                 {
 92 
 93                     myDic[key] = Convert.ToInt32(myDic[key]) + 1;
 94                 }
 95                 else
 96                 {
 97                     myDic.Add(key, value);
 98                 }
 99             }
100         }
101 
102         public void Remove(string key)
103         {
104             if (myDic.ContainsKey(key))
105             {
106                 lock (myDic.SyncRoot)
107                 {
108                     myDic.Remove(key);
109                 }
110             }
111         }
112     }

時間損耗

 1  public static void ReadMethod()
 2         {
 3             Hashtable tempHt = null;
 4             System.Diagnostics.Stopwatch stopwatch = new Stopwatch();
 5             stopwatch.Start(); //  開始監視代碼運行時間
 6             while (true)
 7             {
 8                 Thread.Sleep(100);
 9                 lock (mycoll.myDic.SyncRoot)
10                 {
11                     tempHt = mycoll.myDic.Clone() as Hashtable;
12                 }
13                 Console.WriteLine("\r\n=================================\r\n");
14                 foreach (DictionaryEntry item in tempHt)
15                 {
16                     Console.WriteLine(item.Key + "      " + item.Value);
17                     //其它處理
18                     Thread.Sleep(600);
19                 }
20                 if (tempHt != null && tempHt.Count == 20)
21                 {
22                     break;
23                 }
24             }
25             stopwatch.Stop(); //  停止監視
26             TimeSpan timespan = stopwatch.Elapsed; //  獲取當前實例測量得出的總時間
27             Console.WriteLine("全部加滿用時:" + timespan.Milliseconds);
28         }
29     }

 

好了,多線程安全問題就說到這裡,總結來說就是注意鎖在多線程中的應用。

如有此文章內存在問題,還請多多指正。

 

 


多線程環境下怎正確使用Java集合類

線程安全性是多線程環境下的編程必須面對的棘手的問題.本文從對集合進行迭代常常遇到的java.util.ConcurrentModificationException出發,分析了異常發生的根本原因和底層機理,給出在多線程環境下使用Java集合類的兩個正確方法,一個是將迭代器轉換為數組,另一個是使用並發集合類.掌握了這兩種方法,才能在多線程環境下正確地使用Java集合類.
 

java線程安全的集合不可以保證線程安全?

理論上來說你的product list應該是線程安全的,你把你的代碼貼出來看看呗,看看你是怎麼操作product,又是如何出現數據不一致的
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved