第十四节: 介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。
一. 四大并發(fā)集合類
背景:我們目前使用的所有集合都是線程不安全的?。
A. ConcurrentBag:就是利用線程槽來分?jǐn)侭ag中的所有數(shù)據(jù),鏈表的頭插法,0代表移除最后一個插入的值.
(等價于同步中的List)
B. ConcurrentStack:線程安全的Stack是使用Interlocked來實現(xiàn)線程安全, 而沒有使用內(nèi)核鎖.
(等價于同步中的數(shù)組)
C. ConcurrentQueue: 隊列的模式,先進先出
(等價于同步中的隊列)
D. ConcurrentDictionary: 字典的模式
(等價于同步中的字典)
以上四種安全的并發(fā)集合類,也可以采用同步版本+Lock鎖(或其它鎖)來實現(xiàn)
?代碼實踐:
?
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);}#endregion02-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);}#endregion03-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);}#endregion04-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代碼結(jié)果:
?
二. 隊列的綜合案例
? 上面介紹了四大安全線程集合類和與其對應(yīng)的不安全的線程集合類,可能你會比較疑惑,到底怎么安全了,那些不安全的集合類怎么能變成安全呢,下面以隊列為例,來解決這些疑惑。
1. 測試Queue隊列并發(fā)情況下是不安全的(存在資源競用的問題),ConcurrentQueue隊列在并發(fā)情況下是安全的。
2. 利用Lock鎖+Queue隊列,實現(xiàn)多線程并發(fā)情況下的安全問題,即等同于ConcurrentQueue隊列的效果。
經(jīng)典案例測試:開啟100個線程進行入隊操作,正常所有的線程執(zhí)行結(jié)束后,隊列中的個數(shù)應(yīng)該為100.
①. Queue不加鎖的情況:結(jié)果出現(xiàn)99、98、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());//發(fā)現(xiàn)隊列個數(shù)在不加鎖的情況下 竟然不同 有100,有99Console.WriteLine("Queue不加鎖的情況隊列個數(shù)" + queue.Count);}②. Queue加鎖的情況:結(jié)果全是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 //發(fā)現(xiàn)隊列個數(shù)在全是100 20 Console.WriteLine("Queue加鎖的情況隊列個數(shù)" + queue.Count); 21 }③. ConcurrentQueue不加鎖的情況:結(jié)果全是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 //發(fā)現(xiàn)隊列個數(shù)不加鎖的情形=也全是100,證明ConcurrentQueue是線程安全的 17 Console.WriteLine("ConcurrentQueue不加鎖的情況下隊列個數(shù)" + queue.Count); 18 }3. 在實際項目中,如果使用隊列來實現(xiàn)一個業(yè)務(wù),該隊列需要是全局的,這個時候就需要使用單例(ps:單例是不允許被實例化的,可以通過單例類中的屬性或者方法的形式來獲取這個類),同時,隊列的入隊和出隊操作,如果使用Queue隊列,需要配合lock鎖,來解決多線程下資源的競用問題。
經(jīng)典案例:開啟100個線程對其進行入隊操作,然后主線程輸入隊列的個數(shù),并且將隊列中的內(nèi)容輸出.
結(jié)果:隊列的個數(shù)為100,輸出內(nèi)容1-100依次輸出。
1 /// <summary>2 /// 單例類3 /// </summary>4 public class QueueUtils5 {6 /// <summary>7 /// 靜態(tài)變量:由CLR保證,在程序第一次使用該類之前被調(diào)用,而且只調(diào)用一次8 /// </summary>9 private static readonly QueueUtils _QueueUtils = new QueueUtils(); 10 11 /// <summary> 12 /// 聲明為private類型的構(gòu)造函數(shù),禁止外部實例化 13 /// </summary> 14 private QueueUtils() 15 { 16 17 } 18 /// <summary> 19 /// 聲明屬性,供外部調(diào)用,此處也可以聲明成方法 20 /// </summary> 21 public static QueueUtils instanse 22 { 23 get 24 { 25 return _QueueUtils; 26 } 27 } 28 29 30 //下面是隊列相關(guān)的 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 //發(fā)現(xiàn)隊列個數(shù)在全是100 15 Console.WriteLine("單例模式下隊列個數(shù)" + QueueUtils.instanse.getCount()); 16 17 //下面是出隊相關(guān)的業(yè)務(wù) 18 while (QueueUtils.instanse.getCount() > 0) 19 { 20 Console.WriteLine("出隊:" + QueueUtils.instanse.Dequeue()); 21 } 22 }。。。。。。。。。。。
三. 常見的幾類性能調(diào)優(yōu)
PS:?
1. 常見的一級事件:CPU占用過高、死鎖問題、內(nèi)存爆滿
a. CPU過高:查看是否while(true)中的業(yè)務(wù)過于復(fù)雜,導(dǎo)致cpu一直在高負(fù)荷運行。
b. 死鎖問題:亂用lock,千萬不要lock中再加lock,多個lock重疊
c. 內(nèi)存爆滿:字符串的無限增長,全局的靜態(tài)變量過多。
2. 補充幾個常用的性能調(diào)優(yōu)的方式
a. 使用字典類型Dictionary<T,T>,代替只有兩個屬性的對象或匿名對象。
b. 使用數(shù)組代替只有兩個屬性的對象或匿名對象。
比如:
index:存放id
value:存放數(shù)量或其他屬性
3. 返璞歸真,使用最原始的代碼代替簡潔漂亮的代碼。
4. 合理的使用多線程,業(yè)務(wù)復(fù)雜的盡可能的并發(fā)執(zhí)行(或者異步)。
5. 運用設(shè)計模式,使代碼簡潔、易于擴展。
?
總結(jié)
以上是生活随笔為你收集整理的第十四节: 介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我国今年的经济增速仅1.2%?欧盟更惨,
- 下一篇: 第七节: EF的三种事务的应用场景和各自