第十四節: 介紹四大併發集合類並結合單例模式下的隊列來講明線程安全和非安全的場景及補充性能調優問題。

一. 四大併發集合類設計模式

背景:咱們目前使用的全部集合都是線程不安全的 。數組

  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);
            }
View Code

    ②. 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             }
View Code

    ③. 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             }
View Code

  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. 運用設計模式,使代碼簡潔、易於擴展。

 

 

 

!

  • 做       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說「我是一個小學生」,若有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文連接或在文章開頭加上本人博客地址,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索