一. 四大併發集合類設計模式
背景:咱們目前使用的全部集合都是線程不安全的 。數組
A. ConcurrentBag:就是利用線程槽來分攤Bag中的全部數據,鏈表的頭插法,0表明移除最後一個插入的值.安全
(等價於同步中的List)多線程
B. ConcurrentStack:線程安全的Stack是使用Interlocked來實現線程安全, 而沒有使用內核鎖.併發
(等價於同步中的數組)異步
C. ConcurrentQueue: 隊列的模式,先進先出ide
(等價於同步中的隊列)函數
D. ConcurrentDictionary: 字典的模式性能
(等價於同步中的字典)測試
以上四種安全的併發集合類,也能夠採用同步版本+Lock鎖(或其它鎖)來實現
代碼實踐:
01-ConcurrentBag { Console.WriteLine("---------------- 01-ConcurrentBag ---------------------"); ConcurrentBag<int> bag = new ConcurrentBag<int>(); bag.Add(1); bag.Add(2); bag.Add(33); //鏈表的頭插法,0表明移除最後一個插入的值 var result = 0; //flag爲true,表示移除成功,而且返回被移除的值 var flag = bag.TryTake(out result); Console.WriteLine("移除的值爲:{0}", result); } #endregion 02-ConcurrentStack { Console.WriteLine("---------------- 02-ConcurrentStack ---------------------"); ConcurrentStack<int> stack = new ConcurrentStack<int>(); stack.Push(1); stack.Push(2); stack.Push(33); //鏈表的頭插法,0表明移除最後一個插入的值 var result = 0; //flag爲true,表示移除成功,而且返回被移除的值 var flag = stack.TryPop(out result); Console.WriteLine("移除的值爲:{0}", result); } #endregion 03-ConcurrentQueue { Console.WriteLine("---------------- 03-ConcurrentQueue ---------------------"); ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(33); //隊列的模式,先進先出,0表明第一個插入的值 var result = 0; //flag爲true,表示移除成功,而且返回被移除的值 var flag = queue.TryDequeue(out result); Console.WriteLine("移除的值爲:{0}", result); } #endregion 04-ConcurrentDictionary { Console.WriteLine("---------------- 04-ConcurrentDictionary ---------------------"); ConcurrentDictionary<int, int> dic = new ConcurrentDictionary<int, int>(); dic.TryAdd(1, 10); dic.TryAdd(2, 11); dic.TryAdd(3, 12); dic.ContainsKey(3); //下面是輸出字典中的全部值 foreach (var item in dic) { Console.WriteLine(item.Key + item.Value); } } #endregion
代碼結果:
二. 隊列的綜合案例
上面介紹了四大安全線程集合類和與其對應的不安全的線程集合類,可能你會比較疑惑,到底怎麼安全了,那些不安全的集合類怎麼能變成安全呢,下面以隊列爲例,來解決這些疑惑。
1. 測試Queue隊列併發狀況下是不安全的(存在資源競用的問題),ConcurrentQueue隊列在併發狀況下是安全的。
2. 利用Lock鎖+Queue隊列,實現多線程併發狀況下的安全問題,即等同於ConcurrentQueue隊列的效果。
經典案例測試:開啓100個線程進行入隊操做,正常全部的線程執行結束後,隊列中的個數應該爲100.
①. Queue不加鎖的狀況:結果出現9九、9八、100,顯然是出問題了。
{ Queue queue = new Queue(); object o = new object(); int count = 0; List<Task> taskList = new List<Task>(); for (int i = 0; i < 100; i++) { var task = Task.Run(() => { queue.Enqueue(count++); }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); //發現隊列個數在不加鎖的狀況下 居然不一樣 有100,有99 Console.WriteLine("Queue不加鎖的狀況隊列個數" + queue.Count); }
②. Queue加鎖的狀況:結果全是100,顯然是正確的。
1 { 2 Queue queue = new Queue(); 3 object o = new object(); 4 int count = 0; 5 List<Task> taskList = new List<Task>(); 6 for (int i = 0; i < 100; i++) 7 { 8 var task = Task.Run(() => 9 { 10 lock (o) 11 { 12 queue.Enqueue(count++); 13 } 14 }); 15 taskList.Add(task); 16 } 17 18 Task.WaitAll(taskList.ToArray()); 19 //發現隊列個數在全是100 20 Console.WriteLine("Queue加鎖的狀況隊列個數" + queue.Count); 21 }
③. ConcurrentQueue不加鎖的狀況:結果全是100,顯然是正確,同時證實ConcurrentQueue隊列自己就是線程安全的。
1 { 2 ConcurrentQueue<int> queue = new ConcurrentQueue<int>(); 3 object o = new object(); 4 int count = 0; 5 List<Task> taskList = new List<Task>(); 6 7 for (int i = 0; i < 100; i++) 8 { 9 var task = Task.Run(() => 10 { 11 queue.Enqueue(count++); 12 }); 13 taskList.Add(task); 14 } 15 Task.WaitAll(taskList.ToArray()); 16 //發現隊列個數不加鎖的情形=也全是100,證實ConcurrentQueue是線程安全的 17 Console.WriteLine("ConcurrentQueue不加鎖的狀況下隊列個數" + queue.Count); 18 }
3. 在實際項目中,若是使用隊列來實現一個業務,該隊列須要是全局的,這個時候就須要使用單例(ps:單例是不容許被實例化的,能夠經過單例類中的屬性或者方法的形式來獲取這個類),同時,隊列的入隊和出隊操做,若是使用Queue隊列,須要配合lock鎖,來解決多線程下資源的競用問題。
經典案例:開啓100個線程對其進行入隊操做,而後主線程輸入隊列的個數,而且將隊列中的內容輸出.
結果:隊列的個數爲100,輸出內容1-100依次輸出。
1 /// <summary> 2 /// 單例類 3 /// </summary> 4 public class QueueUtils 5 { 6 /// <summary> 7 /// 靜態變量:由CLR保證,在程序第一次使用該類以前被調用,並且只調用一次 8 /// </summary> 9 private static readonly QueueUtils _QueueUtils = new QueueUtils(); 10 11 /// <summary> 12 /// 聲明爲private類型的構造函數,禁止外部實例化 13 /// </summary> 14 private QueueUtils() 15 { 16 17 } 18 /// <summary> 19 /// 聲明屬性,供外部調用,此處也能夠聲明成方法 20 /// </summary> 21 public static QueueUtils instanse 22 { 23 get 24 { 25 return _QueueUtils; 26 } 27 } 28 29 30 //下面是隊列相關的 31 Queue queue = new Queue(); 32 33 private static object o = new object(); 34 35 public int getCount() 36 { 37 return queue.Count; 38 } 39 40 /// <summary> 41 /// 入隊方法 42 /// </summary> 43 /// <param name="myObject"></param> 44 public void Enqueue(object myObject) 45 { 46 lock (o) 47 { 48 queue.Enqueue(myObject); 49 } 50 } 51 /// <summary> 52 /// 出隊操做 53 /// </summary> 54 /// <returns></returns> 55 public object Dequeue() 56 { 57 lock (o) 58 { 59 if (queue.Count > 0) 60 { 61 return queue.Dequeue(); 62 } 63 } 64 return null; 65 } 66 67 }
1 { 2 int count = 1; 3 List<Task> taskList = new List<Task>(); 4 for (int i = 0; i < 100; i++) 5 { 6 var task = Task.Run(() => 7 { 8 QueueUtils.instanse.Enqueue(count++); 9 }); 10 taskList.Add(task); 11 } 12 13 Task.WaitAll(taskList.ToArray()); 14 //發現隊列個數在全是100 15 Console.WriteLine("單例模式下隊列個數" + QueueUtils.instanse.getCount()); 16 17 //下面是出隊相關的業務 18 while (QueueUtils.instanse.getCount() > 0) 19 { 20 Console.WriteLine("出隊:" + QueueUtils.instanse.Dequeue()); 21 } 22 }
。。。。。。。。。。。
三. 常見的幾類性能調優
PS:
1. 常見的一級事件:CPU佔用太高、死鎖問題、內存爆滿
a. CPU太高:查看是否while(true)中的業務過於複雜,致使cpu一直在高負荷運行。
b. 死鎖問題:亂用lock,千萬不要lock中再加lock,多個lock重疊
c. 內存爆滿:字符串的無限增加,全局的靜態變量過多。
2. 補充幾個經常使用的性能調優的方式
a. 使用字典類型Dictionary<T,T>,代替只有兩個屬性的對象或匿名對象。
b. 使用數組代替只有兩個屬性的對象或匿名對象。
好比:
index:存放id
value:存放數量或其餘屬性
3. 返璞歸真,使用最原始的代碼代替簡潔漂亮的代碼。
4. 合理的使用多線程,業務複雜的儘量的併發執行(或者異步)。
5. 運用設計模式,使代碼簡潔、易於擴展。
!