最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

第 63 講:多線程(五):線程同步的基本概念

2021-10-18 21:32 作者:SunnieShine  | 我要投稿

下面進(jìn)入線程同步的基本內(nèi)容。線程同步不是三兩句能說完的話題,所以可能還會(huì)有比較多的內(nèi)容,希望你做好準(zhǔn)備,特別是心理上的準(zhǔn)備(滑稽

Part 1 什么是線程同步?為什么要線程同步?

我們試著給大家解釋這個(gè)線程同步,那么就需要一個(gè)例子。我們使用一個(gè)最普通的對抗加減法的例子給大家介紹線程同步。

假設(shè)一個(gè)數(shù)字從 0 開始。主線程執(zhí)行減法讓它持續(xù)減少一個(gè)單位;而我們使用線程池調(diào)取一個(gè)線程讓這個(gè)數(shù)不斷增大一個(gè)單位。那么因?yàn)槎嗑€程的不可再現(xiàn)性,我們無法知道結(jié)果是多少。

我們開始運(yùn)行程序。程序運(yùn)行結(jié)果基本上可以說不可能是 0:

而且這個(gè)是其中一種答案。對吧。因?yàn)椴豢稍佻F(xiàn)性你根本控制不了順序計(jì)算的結(jié)果是多少,所以結(jié)果可能天馬行空。

真正的原因是因?yàn)榫€程的不可再現(xiàn)性導(dǎo)致的加法和減法不可能一對一地執(zhí)行,正常的數(shù)學(xué)知識(shí)告訴我們,正常的邏輯下肯定是一加一減最后一定是 0,但在多線程里會(huì)有這樣的情況, 所以結(jié)果是根本不確定的。

還有一個(gè)問題是這里的 ++-- 操作。因?yàn)槲抑熬驼f過, 它被翻譯成增大一個(gè)單位和減小一個(gè)單位后重新賦值給變量本身的過程,因此這個(gè)操作本身也不是原子性的。所以很有可能在底層執(zhí)行的時(shí)候,時(shí)間片一到,線程切換執(zhí)行(即術(shù)語的上下文切換),這個(gè)變量數(shù)值還沒有發(fā)生變化就留給別的線程執(zhí)行去了,所以數(shù)值也會(huì)奇奇怪怪的。

甚至……多線程里還有一個(gè)神奇的賦值過程。如果一個(gè)對象比較大的話(占據(jù)內(nèi)存空間比較多的時(shí)候),在多線程下賦值可能還沒賦值完成就切換上下文了,線程就會(huì)執(zhí)行別的操作,但這個(gè)變量的數(shù)據(jù)都還沒拷貝完。這種錯(cuò)誤線程在術(shù)語詞里稱為 torn read。其中 torn 是 tear 的過去分詞,除了“眼淚”的意思外還可以作為動(dòng)詞,表示“撕裂”的意思,所以這個(gè) torn read 大概就是“讀取撕裂”的意思,暗示數(shù)據(jù)都被撕裂斷開了。

正常情況下,一個(gè)非 torn read 的大小是不超過你電腦位數(shù)這么大的內(nèi)存空間的數(shù)據(jù)可以是原子讀取的。比如說你的電腦是 64 位的,那么就意味著你的電腦可以保證在多線程里,一個(gè) 64 位(一個(gè) long 大小)的變量可以正確地、原子性地讀取成功。但是 C# 里有一個(gè)叫 decimal 的數(shù)據(jù)類型,它占 128 位的空間。如果多線程里拷貝一個(gè) decimal 的變量的話,就可能出現(xiàn) torn read 現(xiàn)象。很有可能多線程還沒有拷貝完這個(gè)數(shù)據(jù),上下文切換了,數(shù)據(jù)就只保留到了其中一部分拷貝成功的這部分的大小。

所以,這個(gè)程序看起來好像只是一個(gè)演示程序,但漏洞百出(起碼在多線程里是漏洞百出)。歸結(jié)到本質(zhì)上,就是因?yàn)樽x寫數(shù)據(jù)都是發(fā)生在同一個(gè)變量(這個(gè)靜態(tài)的字段 Result)上的。因?yàn)閮蓚€(gè)線程同時(shí)都在更改同一個(gè)變量的數(shù)值,所以我們無從知曉多線程的真實(shí)讀寫情況。為了解決這個(gè)問題,我們不得不需要線程同步。

那么,現(xiàn)在就來告訴大家,什么是線程同步。線程同步(Thread Synchronization)就是讓多個(gè)線程在本身不期望這樣執(zhí)行的情況下,以書寫代碼的方式控制這些線程在某個(gè)時(shí)刻下能夠按照我們期望的方式繼續(xù)執(zhí)行下去的行為。換句話說,就是不可控的情況轉(zhuǎn)為可控的情況;而從這個(gè)角度出發(fā),既然我們可以控制線程同步了,那么自然而然地,線程就具備了執(zhí)行正確性、有效性的條件,所以在一些教材書籍上也利用線程同步定義了線程安全的概念:如果能同步多個(gè)線程對代碼或者數(shù)據(jù)的并發(fā)訪問,就可以說這些代碼和數(shù)據(jù)是線程安全的。

注意這個(gè)“并發(fā)”的概念。并發(fā)(Concurrency)指的是多個(gè)線程是一起開始的,就好比比賽的時(shí)候,多個(gè)選手一起從起跑線出發(fā)跑出去,那么比如說 8 位選手要去終點(diǎn),那么我們可以用多線程的角度來解釋這個(gè)現(xiàn)象,就是“8 個(gè)并發(fā)的線程”;而并行,指的是執(zhí)行期間線程和線程之間互不干擾,它們都在執(zhí)行。所以并發(fā)是說明開始的情況,而并行則說明的是期間的過程的情況。這里的概念定義是說,如果多線程是一并開始的,那么它們自然就有了關(guān)聯(lián)(只是說互不影響、互不干擾地執(zhí)行著,但是由我們寫的代碼全部挨個(gè)“發(fā)射出去”的線程,所以受我們控制,因此它們也算是有了關(guān)系)。

Part 2 同步線程的辦法

2-1 使用 Monitor 類型提供的方法鎖定和解除鎖定

要讓多個(gè)線程同步起來,辦法其實(shí)有很多。我們來看第一個(gè)方法:Monitor 類型。Monitor 有兩個(gè)意思,“監(jiān)視器”和“班長”的意思。顯然這里肯定不能翻譯成“班長”所以就只能取第一個(gè)意思了。所謂的“監(jiān)視器”,就是充當(dāng)一個(gè)監(jiān)控?cái)z像頭的角色,監(jiān)控執(zhí)行過程。一旦發(fā)現(xiàn)別的線程在訪問這個(gè)變量但你這個(gè)變量此時(shí)還尚未完成一些行為(比如賦值和自增自減等本不是原子行為但我期望它是原子行為的行為),我可以直接封鎖現(xiàn)場,讓這個(gè)線程先給我卡住,你先不要?jiǎng)樱疫@邊先把數(shù)據(jù)處理了你再來。

翻譯成代碼的話,就是兩個(gè)方法配套起來用:Monitor.EnterMonitor.Exit。這兩個(gè)都是靜態(tài)方法。第一個(gè)方法 Enter 作用是開啟監(jiān)控過程,而 Exit 方法則是退出監(jiān)控過程。所以代碼初步應(yīng)該長這樣:

不過,這里只能說是初步情況。這個(gè)代碼存在兩個(gè)問題。第一是,為什么需要這個(gè) Exit 方法,第二是拋異常了咋辦。

第一,如果這個(gè)監(jiān)控對象如果長期不釋放的話,它就會(huì)起到監(jiān)控的作用導(dǎo)致別的線程無法進(jìn)入這段代碼繼續(xù)執(zhí)行。如果我們不配合 Exit 方法使用的話,只有 Enter 啟用代碼段就會(huì)造成監(jiān)控對象長期占據(jù)執(zhí)行過程,使得任何別的線程都無法繼續(xù)訪問下面的代碼。這就很奇怪了,是吧。所以必須要配合使用。

第二,如果執(zhí)行同步代碼的時(shí)候拋異常咋辦?拋異常不要緊,關(guān)鍵是這個(gè)監(jiān)控對象本身。這個(gè)監(jiān)控對象自己會(huì)犧牲拿來監(jiān)控執(zhí)行過程,可它自己在多線程執(zhí)行期間是不可變動(dòng)的。就好比一個(gè)比賽,上了一個(gè)裁判參與比賽的結(jié)果裁決,結(jié)果比賽期間出現(xiàn)異常情況了,裁判要是不擺脫異常情況這個(gè)困境,他自己就會(huì)卡死在現(xiàn)場。(好吧實(shí)際上是這樣的:選手比如說貧血進(jìn)醫(yī)院了,這個(gè)時(shí)候選手就該醫(yī)生管了,這不屬于裁判的事情,但裁判一直沒有被要求退出比賽過程,那么它就會(huì)在這個(gè)比賽中一直判決已經(jīng)中斷了的比賽過程,可這顯然沒有意義。比賽都中斷了還判個(gè)什么勁兒呢……)所以當(dāng)然裁判先得脫身然后讓一些別的人來看具體選手或者現(xiàn)場都發(fā)生啥異常了,怎么解決,對吧。所以,我們還需要一個(gè) finally 塊來解除這個(gè)監(jiān)控對象的監(jiān)控行為:我需要在 Enter 和同步代碼的外側(cè)加上一個(gè) try 塊,然后 Exit 方法外側(cè)加一個(gè) finally 塊。

只有 tryfinally 就可以。catch 可以“吃掉”異常讓這個(gè)程序繼續(xù)運(yùn)行也不會(huì)閃退,但有些時(shí)候我們更期望它拋出來,反正異常又不是特別嚴(yán)重的問題,不會(huì)造成內(nèi)存溢出的這種復(fù)雜問題,所以拋出異常有時(shí)候還可以幫助我們找一些 bug。不要什么異常都“吃掉”。

這個(gè)“一個(gè)監(jiān)控行為的臨時(shí)對象”和“剛才的那個(gè)監(jiān)控對象”我們稱為同步鎖(Synchronization Lock)。換句話說就是,使用同步鎖來解決線程不同步的問題。

可……finally 里的 if 條件,為什么是線程還在執(zhí)行中,才 Exit 退出監(jiān)控?。坎粦?yīng)該是沒有執(zhí)行的時(shí)候退出監(jiān)控嗎?這個(gè)問題你得反過來想:正是因?yàn)榫€程正在進(jìn)行之中,才需要我們退出監(jiān)控。如果線程都沒有執(zhí)行,那么這個(gè) bool 變量會(huì)保持 false 的數(shù)值。這個(gè)時(shí)候我們退出個(gè)什么勁兒呢?

還記得吧,ref 參數(shù)表示它會(huì)同時(shí)影響調(diào)用方和方法內(nèi)執(zhí)行的同名的變量。這里如果我們沒有 bool 的話,這個(gè)外部傳入的 bool 變量就無法得到記錄。我在某個(gè)時(shí)候變更了這個(gè)變量的數(shù)值,自然我更希望調(diào)用方(這個(gè) try 塊的代碼)立刻知曉這個(gè)變更。所以我當(dāng)然是需要用 ref 修飾參數(shù)了。

那么,回到原來的例子上去,我們需要把 Result++Result-- 包裹起來,用這個(gè) try-finally 包裹起來。

比如這樣。我們重新運(yùn)行程序,在程序運(yùn)行了一會(huì)兒之后,這次我們就可以看到正確的結(jié)果顯示了:

因?yàn)檫@個(gè)時(shí)候,我們同步了代碼之后,多線程雖然看起來是兩個(gè)線程并行執(zhí)行的,但有了同步鎖之后,我們保證了線程必須在執(zhí)行自增自減完成后才能繼續(xù)執(zhí)行別的代碼,這樣就不會(huì)出現(xiàn)多線程的不可再現(xiàn),就是數(shù)據(jù)沒有完成自增自減就切換上下文的問題。

雖然多線程仍會(huì)出現(xiàn)主線程和線程池后臺(tái)線程不知道誰更先執(zhí)行誰更后執(zhí)行的問題,但是我們不妨思考一個(gè)問題。就這個(gè)例子來說,誰先誰后是不是都不影響?我從 0 開始計(jì)算的話,我試著先 +1 后再 -1,和我先 -1 后 +1 計(jì)算出來的結(jié)果是不是都應(yīng)該是 0?所以我們壓根無關(guān)線程本身的誰先誰后,我只需要交替執(zhí)行就可以了。

代碼里的這個(gè) SyncRoot 就不多解釋了吧。我講了同步鎖的概念之后你應(yīng)該就能明白為什么這里叫做 SyncRoot 了吧:sync 是 synchronized 的縮寫,root 則是指代一個(gè)對象。C# 里把所有受到 GC 管控范圍的對象都稱為根。因?yàn)槊恳粋€(gè)元素實(shí)際上都被當(dāng)成了一棵樹的頂級(jí)元素,它們會(huì)牽連別的東西,而它自己的銷毀和使用都會(huì)影響別的元素。所以,剛好作為樹的根出現(xiàn),所以稱為根。而 sync root 其實(shí)就是暗示這個(gè)對象用于這里的線程同步。所以才會(huì)這么取名。另外,你以后會(huì)經(jīng)??吹竭@樣的命名存在。

2-2 使用 lock 關(guān)鍵字

實(shí)際上,lock 關(guān)鍵字在之前就說過了。不過這里再說一次是因?yàn)樗褪怯脕硗骄€程的。而它剛好的底層實(shí)現(xiàn)原理就是用的 Monitor,所以這里可以再說一下。

比如上面的代碼,我們使用的是 Monitor 類型的 ExitEnter 方法。但開發(fā)人員很有可能會(huì)在寫代碼的時(shí)候忘記寫 Exit 方法了啊、忘記 try-finally 了啊之類的。所以 C# 為了避免這樣的現(xiàn)象,用了一個(gè) lock 關(guān)鍵字來避免用戶錯(cuò)誤使用 Monitor 類型的這兩個(gè)方法的調(diào)用。

這樣的代碼效果和之前的寫法沒有差別。這樣寫簡單了,而且?guī)椭碎_發(fā)人員避免了一些書寫代碼上的錯(cuò)誤:lock 后跟的變量就是 Monitor.EnterMonitor.Exit 的同步鎖對象,bool lockTaken 被這個(gè)語句隱藏了不寫出來;lock 語句的開大括號(hào)就意味著 Monitor.Enter 語句的發(fā)出,大括號(hào)里的語句就是 try 塊的后面那部分同步代碼(也叫關(guān)鍵代碼);最后出了這個(gè) lock 語句的大括號(hào)也就等于是調(diào)用了 Monitor.Exit 語句。

2-3 同步鎖的選取

同步鎖的概念不只是在 Monitor.EnterMonitor.Exit 方法里使用,它也在 lock 語句里使用。不過,同步鎖不是所有東西都可以傳入進(jìn)去。這個(gè)監(jiān)控對象要想起到監(jiān)控的效果和作用,很多東西其實(shí)都不允許。我們來看有哪些是不行的。

2-3-1 必須是引用類型,不能是值類型

首先有一個(gè)點(diǎn)要說。我們仔細(xì)觀察 Monitor.EnterMonitor.Exit 方法可以發(fā)現(xiàn),它們傳入的第一個(gè)參數(shù),接收是用的 object 類型接收的。假設(shè)我傳入了一個(gè)值類型的對象進(jìn)去,那么值類型遇到 object 這個(gè)引用類型就必然會(huì)發(fā)生裝箱過程。裝箱的最終結(jié)果是什么呢?在堆內(nèi)存里創(chuàng)建一塊內(nèi)存存儲(chǔ)這個(gè)數(shù)值。但問題就在這里。由于我調(diào)用 Monitor.Exit 的時(shí)候也是 object 類型接收的第一個(gè)參數(shù)。就算是傳入的是相同的數(shù)值過去,但因?yàn)?object 類型接收的關(guān)系,必然會(huì)導(dǎo)致裝箱,裝箱又必然導(dǎo)致創(chuàng)建新內(nèi)存,那么 EnterExit 傳入而產(chǎn)生的裝箱,會(huì)不會(huì)是同一塊內(nèi)存?當(dāng)然不是。

地址都不一樣了,那我監(jiān)控什么?我 EnterExit 的對象都不是一個(gè)東西,而我現(xiàn)在傳參進(jìn)去的變量本身是一個(gè)數(shù)值,Enter 接收的對象是另外一個(gè)數(shù)值,Exit 接收的又是一個(gè)新的數(shù)值,這三個(gè)結(jié)果全部各自都沒關(guān)系。所以我使用值類型就必然導(dǎo)致我無法釋放同步鎖的監(jiān)控狀態(tài)。因此,我們不建議使用值類型作為同步鎖的對象的實(shí)例。

2-3-2 必須是 private 修飾的

這個(gè)其實(shí)很好理解,至少比剛才不讓用值類型的約束好理解多了。同步鎖要求我臨時(shí)監(jiān)控?cái)?shù)據(jù)信息,它主要體現(xiàn)的作用在“臨時(shí)”上。如果我 public 化或者 protected 化,或者 internal 化這個(gè)同步鎖,就勢必可能在任何一處別的位置調(diào)用到它。而此時(shí) private 才是你寫代碼的可控范疇。只有 private 的修飾符才完全受你寫代碼的掌控。如果你寫的是比如 protected 的修飾符的話,這個(gè)同步鎖必然就可以在派生類里看到它。不論派生類是你自己寫的,還是別人用的你這套 API 寫的,這個(gè)同步鎖就必然會(huì)被看到,于是對方就可能會(huì)拿來干壞事,或者不知情的情況下用來做別的事情。同步鎖只用來同步線程,而且是你在控制范疇下同步線程,所以不能容許任何其它情況使用它。當(dāng)然,你寫了 private 結(jié)果你自己又在亂使用這個(gè)同步鎖對象,那么……當(dāng)我沒說。

2-3-3 必須是 static 修飾的

同步鎖應(yīng)該確保我任何必需的時(shí)候都可以即刻拿到對象。所以我必須加上 static 修飾符,讓它在程序開始運(yùn)行的時(shí)候就創(chuàng)建好它。這樣我才不至于我還得實(shí)例化對象了之后才能使用它。要知道,實(shí)例成員和靜態(tài)成員調(diào)用都可以使用靜態(tài)對象,但這個(gè)靜態(tài)對象只能在靜態(tài)成員里進(jìn)行調(diào)用,它們不是對稱的關(guān)系。

2-3-4 必須是 readonly 修飾的

同步鎖必須確保對象只讀。不然你隨便篡改修改對象的數(shù)值,就起不到同步鎖該起到的作用。它是一個(gè)鎖,用來同步線程,你篡改它的數(shù)值也沒有任何意義。

2-3-5 最好是一個(gè)字段

既然有 private、staticreadonly 修飾,那么能產(chǎn)生這樣的情況的成員類別只有字段和屬性兩種了。但是屬性的話,每一次都會(huì)產(chǎn)生一個(gè)新的對象(用 get 方法):

這么寫是沒錯(cuò), 但……每次我用 SyncRoot 我都創(chuàng)建新對象,是不是有點(diǎn)欠妥?屬性本質(zhì)還是兩個(gè)方法構(gòu)成的(getset),所以還是不建議使用。因此能用的就只剩下字段了。

等會(huì)兒。thistypeof 表達(dá)式這些東西其實(shí)也都可以。所以先別急。先繼續(xù)看看后面的內(nèi)容吧。

2-3-6 避免 lock (this)

眾所周知,小括號(hào)里的東西是直接傳入到 Monitor 的那兩個(gè)方法里當(dāng)參數(shù)使用的。那么既然是參數(shù)就必然是什么可能情況都會(huì)有,比如傳入一個(gè)字符串字面量進(jìn)去,比如我傳入兩個(gè)字符串拼接的表達(dá)式結(jié)果過去(比如 a + b 什么的),我甚至傳入一個(gè) typeof 表達(dá)式也行的。對吧。因?yàn)?object 對象接收和支持任何非指針類型的對象賦值過去。

我們來說一下,這些東西到底可以不可以。首先來說 this 對象。this 表示當(dāng)前實(shí)例成員的環(huán)境下可以使用的特殊對象,它表示當(dāng)前對象自己。具體在調(diào)用和計(jì)算的時(shí)候,是什么對象,就把這個(gè)對象當(dāng)成 this 替換替代過去就可以了??墒?,我如果把同步鎖用 this 的話,且不說它是不是值類型,我們就假設(shè)它是引用類型,思考一下可不可以。

顯然不好。因?yàn)槲抑苯影褜ο蟊旧砟脕懋?dāng)監(jiān)控對象的話,對象自己就當(dāng)裁判上場了(剛才那個(gè)舉例)。那么問題來了,你在這個(gè)比賽上當(dāng)裁判,可不可能別的比賽也需要你?完全是有可能的嘛。你這個(gè) this 只是一個(gè)代號(hào),它可以替換為我們具體執(zhí)行的一個(gè)對象。而問題就在于,this 關(guān)鍵字代替的這個(gè)對象是從外界代碼里傳入的對象的一個(gè)抽象概念,而外界代碼你是無法控制的,你完全可能會(huì)在別處產(chǎn)生兩個(gè)對象同時(shí)被 lock 鎖掉。

這么說有點(diǎn)抽象,我來舉個(gè)例子。假設(shè)我一個(gè)方法 P 包含了 lock 語句鎖的是 this 對象。

而我完全不知情。我在外面調(diào)用代碼我是看不到 P 里面的代碼的。現(xiàn)在我試著調(diào)用代碼的時(shí)候:

這么做可以,對吧。它會(huì)把我們傳過來的 i 鎖住。這個(gè) i 是實(shí)例,它就替換到底層代碼里的 this,做一個(gè)替換。

可問題是,我在后續(xù)的代碼里還在用 i,咋辦?同步鎖是只用于同步線程的,它自己是實(shí)例本身就是用作計(jì)算和調(diào)用實(shí)例應(yīng)該做的事情,而現(xiàn)在它又在做同步線程的事情,至少從良構(gòu)類型里就算是違背了單一職責(zé)原則了吧。這足夠我寄刀片過了吧。

再說了,你這個(gè) i 進(jìn)入 P 后就被鎖住了,我還拿來做別的事情,顯然是不行的。你當(dāng)裁判可以看兩個(gè)選手在干什么,可以建立線程和線程的關(guān)系(通過你自己),但你自己是一個(gè)單獨(dú)的對象啊,你在這個(gè)時(shí)候只能做一件事情,就是當(dāng)裁判。這個(gè)時(shí)候你裁判只能做裁判的事,別的事情都做不了。你想想,是不是這個(gè)道理。

2-3-7 避免 lock (typeof(T))

this 都不允許了,那么 typeof 應(yīng)該也不合適了。typeof 是什么?typeof 是一個(gè)表達(dá)式,它最后會(huì)得到一個(gè)固有實(shí)例,這個(gè)實(shí)例可用來參與和計(jì)算你這個(gè)類型的基本反射信息。但是,難不成我兩次調(diào)用 typeof(int),會(huì)產(chǎn)生兩個(gè)不同的對象嗎?肯定不可能。因?yàn)槲页绦蚣?int 的反射信息我只需要一份就行了。因?yàn)椋硎具@個(gè) int 類型的字段、屬性有多少個(gè),分別是哪些。這些信息在程序集跑起來、運(yùn)行起來的時(shí)候是不可變的,因?yàn)樗鼈兌荚谠獢?shù)據(jù)里。

既然都不可變了,那么我兩次調(diào)用 typeof(int),你想想會(huì)不會(huì)可能是兩個(gè)不同的對象?是的,C# 的反射機(jī)制會(huì)使得兩次調(diào)用獲取的是同一個(gè)反射信息提供對象。

那么,既然能產(chǎn)生同樣的實(shí)例,就必然會(huì)出現(xiàn)剛才 this 的錯(cuò)誤使用情況。我拿一個(gè)當(dāng)裁判,結(jié)果又讓它去做別的事情,顯然不可能。所以 typeof(T) 也不適合用于同步鎖。

2-3-8 避免字符串類型的變量作同步鎖

最后。字符串也是一個(gè)引用類型,看起來它可以實(shí)例化在字段里:

好像前面的條件我都滿足,那么字符串是否適合用于同步鎖呢?

答案是否定的。原因在于字符串的底層存儲(chǔ)機(jī)制。在 C# 的底層,字符串會(huì)按照引用類型的操作進(jìn)行實(shí)例化、賦值、取值和計(jì)算。但是它和普通對象不同的地方在于,字符串擁有一個(gè)和普通數(shù)據(jù)類型不同的存儲(chǔ)機(jī)制:拘留池(Intern Pool)。

為了優(yōu)化使用字符串,C# 允許相同的字符串實(shí)例會(huì)緩存到拘留池里,使得它們的地址也相同。舉個(gè)例子。

你猜猜這個(gè)計(jì)算結(jié)果是什么?我們知道即使數(shù)值相同,但地址不一致,是引用類型的基本操作了已經(jīng)。但是字符串因?yàn)樽址畠?nèi)容一致的關(guān)系,較短的字符串會(huì)被丟進(jìn)拘留池里,因此 st 會(huì)保持一致的地址數(shù)值。因此不論比較 ReferenceEquals 還是比較字符串內(nèi)容,st 都應(yīng)該是一樣的,因?yàn)樗鼈兙褪峭粋€(gè)對象。

既然如此,字符串進(jìn)入了拘留池,那么如果你實(shí)例化了一個(gè)相同字面量的字符串,也很有可能因?yàn)榫辛舫卮嬗羞@個(gè)字符串,因而導(dǎo)致兩個(gè)字符串是同一個(gè)變量。所以,這又回到了剛才 this、typeof 表達(dá)式里那個(gè)問題了。

因此,字符串也不建議作為同步鎖對象。

2-4 用 MethodImplAttribute 特性

C# 里有一個(gè)神奇的特性,可以在標(biāo)記了方法之后改變方法在運(yùn)行時(shí)的執(zhí)行效果。

其實(shí),也不算神奇。因?yàn)樘匦跃褪悄脕碜鲞@個(gè)事的。特性就是高配版的注釋文字,給成員啊、參數(shù)打上標(biāo)簽,這樣以后我可以通過反射獲取它們,改變執(zhí)行意義,這確實(shí)是特性本來的目的。

MethodImplAttribute 特性可以改變方法執(zhí)行的時(shí)候的行為。它需要傳入一個(gè)參數(shù),是 MethodImplOptions 類型的枚舉,其中有一個(gè)數(shù)值是 MethodImplOptions.Synchronized,一旦標(biāo)記上去后,方法整體就是方法級(jí)的線程同步形式執(zhí)行了。

用法是這樣的:

這樣的話,整個(gè)方法基本等價(jià)于這樣:

是的。當(dāng)然,這是實(shí)例方法。如果方法是靜態(tài)方法的話,標(biāo)記這個(gè)特性上去,就不是等價(jià)于 lock (this) 了,而是等價(jià)于包裹的 lock (typeof(類型)) 了。但是前文說過,我們不建議使用 lock (this)lock (typeof(類型)) 的東西,所以,這個(gè)特性我們也不建議使用。

2-5 使用 Interlocked 類型提供的方法

是的。如果單純只是解決自增和自減方法的話,你可以使用 Interlocked 靜態(tài)類型里提供的 IncrementDecrement 方法,來達(dá)到即使沒有使用 lock 語句也可以享受 lock 環(huán)境下的鎖定行為。

你甚至不必寫出 lock 語句,直接替換掉原本的 ++--,改成 Interlocked.Increment(ref Result)Interlocked.Decrement(ref Result) 就可以了:

你甚至不需要寫 SyncRoot。這樣運(yùn)行的結(jié)果照樣是 0:

因?yàn)樗亲詭У逆i定行為,因此我們不需要 SyncRoot 也可以達(dá)到鎖定效果;另外,這個(gè)方法不是 C# 語法能實(shí)現(xiàn)的,因此你即使查看源代碼也無法找到它的源代碼;雖然看不到代碼,但我們?nèi)匀煌扑]你使用這個(gè)方法來達(dá)到自增自減和 lock 語句等同的行為。

當(dāng)然,除了使用 DecrementIncrement 方法外,Interlocked 還提供了一個(gè)等價(jià)的加減法運(yùn)算方法:Add。這個(gè)方法可以允許我們對一個(gè)數(shù)值指定增大或減小多少,等價(jià)于一般代碼的 +=-= 運(yùn)算符。

比如這樣就跟 Interlocked.Decrement(ref Result) 是一個(gè)效果。

第 63 講:多線程(五):線程同步的基本概念的評論 (共 條)

分享到微博請遵守國家法律
旬阳县| 儋州市| 金华市| 邻水| 原阳县| 霍州市| 白山市| 海安县| 山阴县| 宝坻区| 崇左市| 达孜县| 巫山县| 邻水| 法库县| 开鲁县| 祁东县| 阜平县| 福贡县| 柯坪县| 南昌市| 云南省| 浦江县| 策勒县| 乐平市| 伊金霍洛旗| 望江县| 怀安县| 博兴县| 江达县| 静宁县| 辉南县| 略阳县| 朝阳区| 湖南省| 阿城市| 秦皇岛市| 辽宁省| 衢州市| 武夷山市| 海淀区|