如何證明 ConcurrentDictionary 字典操作不全是線程安全的

前言

最近,看到一篇文章,講到《ConcurrentDictionary字典操作竟然不全是線程安全的?》。

首先,這個結論是正確的,但文中給出的一個證明例子,我覺得是有問題的。

相關代碼如下:

using?System.Collections.Concurrent;public?class?Program
{private?static?int?_runCount?=?0;private?static?readonly?ConcurrentDictionary<string,?string>?_dictionary=?new?ConcurrentDictionary<string,?string>();public?static?void?Main(string[]?args){var?task1?=?Task.Run(()?=>?PrintValue("The?first?value"));var?task2?=?Task.Run(()?=>?PrintValue("The?second?value"));var?task3?=?Task.Run(()?=>?PrintValue("The?three?value"));var?task4?=?Task.Run(()?=>?PrintValue("The?four?value"));Task.WaitAll(task1,?task2,?task4,task4);PrintValue("The?five?value");Console.WriteLine($"Run?count:?{_runCount}");}public?static?void?PrintValue(string?valueToPrint){var?valueFound?=?_dictionary.GetOrAdd("key",x?=>{Interlocked.Increment(ref?_runCount);Thread.Sleep(100);return?valueToPrint;});Console.WriteLine(valueFound);}
}

3ae358c178ef28ec14bb2ccec0536130.png

那這個例子是不是能夠說明 ConcurrentDictionary 字典操作不是線程安全的呢?

首先,讓我們看看什么是“線程安全”。

線程安全

線程安全:當多個線程同時訪問時,保證實現沒有爭用條件。

這里的“爭用條件”又是什么呢?下面舉個例子來說明。

假設兩個線程各自將全局整數變量的值遞增 1。理想情況下,將發生以下操作序列:

線程 1線程 2
整數值



0
讀取值
0
增加值

0
回寫
1

讀取值1

增加值
1

回寫2

在上面顯示的情況下,最終值為 2,如預期的那樣。但是,如果兩個線程在沒有鎖定或同步的情況下同時運行,則操作的結果可能是錯誤的。下面的替代操作序列演示了此方案:

線程 1線程 2
整數值



0
讀取值
0

讀取值0
增加值

0

增加值
0
回寫
1

回寫1

在這種情況下,最終值為 1,而不是預期的結果 2。發生這種情況是因為此處的增量操作不是互斥的。互斥操作是在訪問某些資源(如內存位置)時無法中斷的操作。

如果用那篇文章的例子,演示是否線程安全的代碼應該是這樣的:

using?System.Collections.Concurrent;public?class?Program
{private?static?int?_runCount?=?0;private?static?int?_notsafeCount?=?0;public?static?void?Main(string[]?args){var?tasks?=?new?Task[100];for?(int?i?=?0;?i?<?tasks.Length;?i++){tasks[i]?=?Task.Run(()?=>?PrintValue($"The?{i}?value"));}Task.WaitAll(tasks);Console.WriteLine($"Run?count:?{_runCount}");Console.WriteLine($"Not?Safe?Count:?{_notsafeCount}");}public?static?void?PrintValue(string?valueToPrint){Interlocked.Increment(ref?_runCount);_notsafeCount++;Thread.Sleep(100);}
}

我們把 Task 數量加大到 100,便于查看效果。

執行 3 次,_runCount 始終等于 100,因為Interlocked是線程安全的,而 _notsafeCount 的值卻是隨機的,說明 PrintValue 方法不是線程安全的。

48dfb6a4deda2bef5ce4689ddcb85852.png

GetOrAdd

讓我們再把 PrintValue 方法改成使用 GetOrAdd:

public?static?void?PrintValue(string?valueToPrint)
{var?valueFound?=?_dictionary.GetOrAdd("key",x?=>{Interlocked.Increment(ref?_runCount);_notsafeCount++;Thread.Sleep(100);return?valueToPrint;});Console.WriteLine(valueFound);
}

再執行 3 次,我們發現,_notsafeCount 的值始終和 _runCount 的值相同,貌似沒出現線程爭用。

a882b2d61f525303392f386bfc599f6c.png

大家看到這是不是有點懵逼,這不反而證明了,

ConcurrentDictionary字典操作是線程安全的!

真是這樣嗎?

這也正是我認為原文的例子不太恰當的原因:它只證明了有多個線程進入,而沒證明出現了線程爭用,無法得到線程不安全的結論。

從上面線程不安全的例子我們看到,一共 100 個 Task 執行而?_notsafeCount 的值都是 90 多,這說明線程爭用很難被觸發。而上面的操作只執行了 8 次,也許是還沒觸發線程爭用呢?

我們修改代碼,每進入 1 次 valueFactory 就執行 10 次 _notsafeCount++:

public?static?void?PrintValue(string?valueToPrint)
{var?valueFound?=?_dictionary.GetOrAdd("key",x?=>{Interlocked.Increment(ref?_runCount);for?(int?i?=?0;?i?<?10;?i++){_notsafeCount++;Thread.Sleep(100);}return?valueToPrint;});Console.WriteLine(valueFound);
}

68131f29f9db4d813b8599fb90d13a9f.png

理論上,_notsafeCount 應該等于 90(9*10),而實際上輸出 88,這說明出現了線程爭用。

也就是說,ConcurrentDictionary 的 GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) 方法不是線程安全的。

這個結論從?GetOrAdd 方法的源碼也可以得到驗證,執行 valueFactory(key)?時是沒加鎖的:

public?TValue?GetOrAdd(TKey?key,?Func<TKey,?TValue>?valueFactory)
{if?(key?is?null){ThrowHelper.ThrowKeyNullException();}if?(valueFactory?is?null){ThrowHelper.ThrowArgumentNullException(nameof(valueFactory));}IEqualityComparer<TKey>??comparer?=?_comparer;int?hashcode?=?comparer?is?null???key.GetHashCode()?:?comparer.GetHashCode(key);if?(!TryGetValueInternal(key,?hashcode,?out?TValue??resultingValue)){TryAddInternal(key,?hashcode,?valueFactory(key),?updateIfExists:?false,?acquireLock:?true,?out?resultingValue);}return?resultingValue;
}

總結

如果你想驗證某個方法是否線程安全,都可以用上面這種觸發線程爭用方式。

還不趕緊試試?!?

添加微信號【MyIO666】,邀你加入技術交流群

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282671.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282671.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282671.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

微型計算機及接口技術試題,1月自考微型計算機及其接口技術試題及答案解析...

《1月自考微型計算機及其接口技術試題及答案解析》由會員分享&#xff0c;可在線閱讀&#xff0c;更多相關《1月自考微型計算機及其接口技術試題及答案解析(11頁珍藏版)》請在人人文庫網上搜索。1、精品自學考試資料推薦全國 2018年 1月自考微型計算機及其接口技術試題課程代碼…

16-djongo中間件學習

目錄 前戲 我們在前面的課程中已經學會了給視圖函數加裝飾器來判斷是用戶是否登錄&#xff0c;把沒有登錄的用戶請求跳轉到登錄頁面。我們通過給幾個特定視圖函數加裝飾器實現了這個需求。但是以后添加的視圖函數可能也需要加上裝飾器&#xff0c;這樣是不是稍微有點繁瑣。 學完…

PHP基礎(必須熟練掌握的基礎)

<?php/*** 三元運算符的應用*/ /* $a 10; $b 15; echo $a > $b ? 1 : 0; */ // 注:php7新添加的運算符比較運算符x<>y // 如果x和y相等,就返回0,如果x>y,就返回1,如果x的值小于y,就返回-1/* $a "aaa"; $b "bbb"; echo $a.$b; *//*** …

子進程無法從標準輸入讀取數據

每個process對象最多只能調用一次start()方法&#xff0c;join([timeout])方法會阻塞調用process對象的進程&#xff0c;直到timeout時間超時&#xff0c;或者process進程退出。如果timeout設置為None&#xff0c;則無超時時間。對于linux操作系統的進程管理&#xff0c;父進程…

Eclipse控制項目的訪問名稱

Eclipse控制web項目的訪問名稱 web項目的訪問路徑&#xff08;名稱&#xff09;修改 1.點擊項目右鍵-》properties找到Context root 修改成我們需要的名字即可轉載于:https://www.cnblogs.com/pypua/articles/7379950.html

計算機一級選擇題已做完確認,計算機一級選擇題(附答案)

點擊藍字關注我們(1)按照需求功能的不同&#xff0c;信息系統已形成各種層次&#xff0c;計算機應用于管理是開始于:()A)信息處理B)人事管理C)決策支持D)事務處理正確答案&#xff1a;A解析&#xff1a;計算機用于管理&#xff0c;起源于計算機在辦公應用中對大量信息、數據的處…

參加51CTO培訓,PMP考試通過啦

為什么選擇考PMP&#xff1f;先介紹下自己的情況&#xff0c;畢業三年&#xff0c;單位類似于平臺&#xff0c;不做技術&#xff0c;常態的工作是文案、商務、市場都會涉及些&#xff0c;對未來也有些迷茫。受前輩點撥可以學一些通用的技能&#xff0c;于是我選擇了PMP&#xf…

如何查看服務器并發請求連接數

https://wenku.baidu.com/view/fb553d795acfa1c7aa00cc27?pcf2#1 轉載于:https://www.cnblogs.com/linewman/p/9918760.html

C# 二十年語法變遷之 C# 5 和 C# 6參考

C# 二十年語法變遷之 C# 5 和 C# 6參考https://benbowen.blog/post/two_decades_of_csharp_ii/自從 C# 于 2000 年推出以來&#xff0c;該語言的規模已經大大增加&#xff0c;我不確定任何人是否有可能在任何時候都對每一種語言特性都有深入的了解。因此&#xff0c;我想寫一系…

非涉密計算機檢查的通知,關于開展非涉密計算機及可移動存儲介質專項清理活動的緊急通知...

關于在全校范圍內開展非涉密計算機及可移動存儲介質專項清理活動的緊急通知密辦字[2009]01號各單位&#xff1a;為有效遏制木馬病毒和惡意代碼的蔓延趨勢&#xff0c;現在校內開展一次非涉密計算機及可移動存儲介質的專項清理活動&#xff0c;要求如下&#xff1a;1、所有涉密人…

Spring Cloud構建微服務架構:服務消費(基礎)

使用LoadBalancerClient在Spring Cloud Commons中提供了大量的與服務治理相關的抽象接口&#xff0c;包括DiscoveryClient、這里我們即將介紹的LoadBalancerClient等。對于這些接口的定義我們在上一篇介紹服務注冊與發現時已經說過&#xff0c;Spring Cloud做這一層抽象&#x…

oracle數據庫中VARCHAR2(50 CHAR) 和VARCHAR2(50) 有啥區別?

VARCHAR2&#xff08;50 char&#xff09;這種類型的字段最多放50個字符&#xff0c;不夠50個用空格填充&#xff1b;而VARCHAR2(50)最大允許存放50個字符&#xff0c;但是不足50個也不用空格填充。varchar2是變長字符串&#xff0c;與CHAR類型不同&#xff0c;它不會使用空格填…

《解密小米之互聯網下的商業奇跡》

解密小米《解密小米之互聯網下的商業奇跡》 磐石之心 清華大學出版社 2014/10/1 書籍&#xff1a;《非同凡響想,喬布斯啟示錄》 磐石之心&#xff1a;原名王斌&#xff0c;互聯網IT資深預言家&#xff0c;第一個提出互聯網未來競爭是在線生活方式的競爭&#xff1b;第一個提出3…

計算機內存的故障,計算機內存出現故障的解決方法

內存如果出現故障&#xff0c;會造成系統運行不穩定、程序異常出錯和*作系統無法安裝的故障&#xff0c;下面將列舉內存常見的故障排除實例。1)內存順序引起的計算機工作不正常故障現象&#xff1a;一臺p4計算機&#xff0c;使用的是華碩intel850芯片組的主板&#xff0c;兩條r…

2018暑假集訓---遞推遞歸----一只小蜜蜂hdu2044

一只小蜜蜂... Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 93249 Accepted Submission(s): 33187Problem Description 有一只經過訓練的蜜蜂只能爬向右側相鄰的蜂房&#xff0c;不能反向爬行。請編程計算蜜…

《ASP.NET Core 6框架揭秘》實例演示[28]:自定義一個服務器

作為ASP.NET Core請求處理管道的“龍頭”的服務器負責監聽和接收請求并最終完成對請求的響應。它將原始的請求上下文描述為相應的特性&#xff08;Feature&#xff09;&#xff0c;并以此將HttpContext上下文創建出來&#xff0c;中間件針對HttpContext上下文的所有操作將借助于…

高清攝像頭MIPI接口與ARM連接【轉】

本文轉載自&#xff1a;http://www.cnblogs.com/whw19818/p/5811299.html MIPI攝像頭常見于手機、平板中&#xff0c;支持500萬像素以上高清分辨率。它的全稱為“Mobile Industry Processor Interface”&#xff0c;分為MIPI DSI 和MIPI CSI&#xff0c;分別對應于視頻顯示和視…

算法(第4版)Robert Sedgewick 刷題 第一章(1)

/*** Description 顛倒數組排列順序* author SEELE* date 2017年8月17日 上午10:56:17* action sortArr*/public static void sortArr() {int[] b new int[6];int[] a { 1, 2, 3, 4, 5, 6, 7 };for (int i 0; i < a.length / 2; i) {int temp a[a.length - 1 - i];a[a.l…

9種排序算法在四種數據分布下的速度比較

9種算法分別是&#xff1a; 1.選擇排序 2.希爾排序 3.插入排序 4.歸并排序 5.快速排序 6.堆排序 7.冒泡排序 8.梳排序 9.雞尾酒排序 在不同的情形下&#xff0c;排序速度前三名也不盡相同 Random : 希爾>快排>歸并 Few unique : 快排>…

win7服務器端口被占用,高手親自幫您win7端口被占用的詳盡處理要領

今天有一位用戶說他安裝了win7系統以后&#xff0c;在使用中突然遇到了win7端口被占用的情況&#xff0c;估計還會有更多的網友以后也會遇到win7端口被占用的問題&#xff0c;所以今天我們先來分析分析&#xff0c;那我們要怎么面對這個win7端口被占用的問題呢&#xff1f;大家…