線程安全性 Java并發(fā)編程實戰(zhàn)總結

在構建穩(wěn)健的并發(fā)程序時铅辞,必須正確地使用線程和鎖。但這些終歸只是一些機制。 要編寫線程安全的代碼,其核心在于要對狀態(tài)訪問操作進行管理层宫, 特別是對共享的(Shared)和可變的(Mutable)狀態(tài)的訪問

? ?????從非正式的意義上來說其监, 對象的狀態(tài)是指存儲在狀態(tài)變量(例如實例或靜態(tài)域)中的數(shù)據(jù)萌腿。對象的狀態(tài)可能包括其他依賴對象的域。 例如抖苦,某個HashMap的狀態(tài)不僅存儲在HashMap對象本身毁菱,還存儲在許多Map.Entry對象中。 在對象的狀態(tài)中包含了任何可能影響其外部可見行為的數(shù)據(jù)锌历。

?????????“共享 ” 意味著變量可以由多個線程同時訪問贮庞, 而 “可變” 則意味著變量的值在其生命周期內可以發(fā)生變化。 我們將像討論代碼那樣來討論線程安全性辩涝, 但更側重于如何防止在數(shù)據(jù)上發(fā)生不受控的并發(fā)訪問贸伐。

????????一個對象是否需要是線程安全的勘天,取決于它是否被多個線程訪問怔揩。這指的是在程序中訪問對象的方式, 而不是對象要實現(xiàn)的功能脯丝。 要使得對象是線程安全的商膊, 需要采用同步機制來協(xié)同對對象可變狀態(tài)的訪問。如果無法實現(xiàn)協(xié)同宠进, 那么可能會導致數(shù)據(jù)破壞以及其他不該出現(xiàn)的結果晕拆。

????????當多個線程訪問某個狀態(tài)變量并且其中有一個線程執(zhí)行寫入操作時, 必須采用同步機制來協(xié)同這些線程對變量的訪問材蹬。Java中的主要同步機制是關鍵字synchronized, 它提供了一種獨占的加鎖方式实幕,但“ 同步” 這個術語還包括volatile類型的變量,顯式鎖(Explicit Lock)以及原子變量堤器。

????????在上述規(guī)則中并不存在一些想象中的“例外” 情況昆庇。即使在某個程序中省略了必要同步機制并且看上去似乎能正確執(zhí)行,而且通過了測試并在隨后幾年時間里都能正確地執(zhí)行闸溃,但程序仍可能在某個時刻發(fā)生錯誤整吆。


? ??????如果在設計類的時候沒有考慮并發(fā)訪問的情況拱撵,那么在采用上述方法時可能需要對設計進行重大修改,因此要修復這個問題可謂是知易行難表蝙。如果從一開始就設計一個線程安全的類拴测,那么比在以后再將這個類修改為線程安全的類要容易得多。

????????在一些大型程序中府蛇,要找出多個線程在哪些位置上將訪問同一個變量是非常復雜的集索,面向對象這種技術不僅有助于編寫出結構優(yōu)雅、可維護性高的類欲诺,還有助于編寫安全的類抄谐。訪問某個變量的代碼越少,就越容易確保對變量的所有訪問都實現(xiàn)正確同步扰法,也更容易找出變量在哪些條件下被訪問蛹含。Java語言并沒有強制要求將狀態(tài)都封裝在類中,開發(fā)人員完全可以將狀態(tài)保存在某個公開的域(甚至公開的靜態(tài)域) 中塞颁,或者提供一個對內部對象的公開引用浦箱。然而,程序狀態(tài)的封裝性越好祠锣,就越容易實現(xiàn)程序的線程安全性酷窥,并且代碼的維護人員也越容易保持這種方式。

? ??????當設計線程安全的類時伴网,良好的面向對象技術蓬推、不可修改性,以及明晰的不變性規(guī)范都能起到一定的幫助作用澡腾。

? ??????在某些情況中沸伏,良好的面向對象設計技術與實際情況的需求并不一致。在這些情況中动分,可能需要犧牲一些良好的設計原則毅糟,以換取性能或者對遺留代碼的向后兼容。有時候澜公,面向對象中的抽象和封裝會降低程序的性能(盡管很少有開發(fā)人員相信)姆另,但在編寫并發(fā)應用程序時,一種正確的編程方法就是:首先使代碼正確運行坟乾,然后再提高代碼的速度迹辐。即便如此,最好也只是當性能測試結果和應用需求告訴你必須提高性能甚侣,以及測最結果表明這種優(yōu)化在實際環(huán)境中確實能帶來性能提升時明吩,才進行優(yōu)化。

????????如果你必須打破封裝渺绒,那么也并非不可以贺喝,你仍然可以實現(xiàn)程序的線程安全性菱鸥,只是更困難,而且躏鱼,程序的線程安全性將更加脆弱氮采,不僅增加了開發(fā)的成本和風險,而且也增加了維護 的成本和風險染苛。第4章詳細介紹了在哪些條件下可以安全地放寬狀態(tài)變址的封裝性鹊漠。

????????到目前為止,我們使用了 “線程安全類” 和 “線程安全程序” 這兩個術語茶行,二者的含義基本相同躯概。線程安全的程序是否完全由線程安全類構成?答案是否定的畔师,完全由線程安全類構成 的程序并不一定就是線程安全的娶靡,而在線程安全類中也可以包含非線程安全的類。在任何情況中看锉, 只有當類中僅包含自己的 狀態(tài)時姿锭,線程安全類才是有意義的。線程安全性是一個在代碼上使用的術語伯铣, 但它只是與狀態(tài)相關的呻此,因此只能應用于封裝其狀態(tài)的整個代碼,這可能是一個對象腔寡,也可能是整個程序焚鲜。


什么是線程安全性

? ? ? ? 在線程安全性的定義中, 最核心的概念就是正確性放前。如果對線程安全性的定義是模糊的忿磅,那么就是因為缺乏對正確性的清晰定義。正確性的含義是犀斋, 某個類的行為與其規(guī)范完全一致贝乎。在良好的規(guī)范中通常會定義各種不變性條件(Invariant)來約束對象的狀態(tài)情连,以及定義各種后驗條件(Postcondition)來描述對象操作的結果叽粹。由于我們通常不會為類編寫詳細的規(guī)范,那么如何知道這些類是否正確呢却舀?我們無法知道虫几,但這并不妨礙我們在確信“類的代碼能工作” 后使用它們。這種“代碼可信性” 非常接近于我們對正確性的理解挽拔,因此我們可以將單線程的正確性近似定義為“所見即所知(we knowit when we see it)"辆脸。在對“正確性” 給出了一個較為清晰的定義后,就可以定義線程安全性: 當多個線程訪問某個類時螃诅,這個類始終都能表現(xiàn)出正確的行為啡氢,那么就稱這個類是線程安全的状囱。

? ? ? ? 當多個線程訪問某個類時,不管運行時環(huán)境采用何種調度方式或者這些線程將如何交替執(zhí)行倘是,并且在調度代碼中不需要任何額外的同步或協(xié)同亭枷,這個類都能表現(xiàn)出正確的行為,那就可以稱這個類是線程安全的搀崭。

? ??????由于單線程程序也可以看成是一個多線程程序叨粘,如果某個類在單線程環(huán)境中都不是正確的,那么它肯定不會是線程安全的瘤睹。如果正確地實現(xiàn)了某個對象升敲,那么在任何操作中(包括調用對象的公有方法或者對其公有域進行讀/寫操作)都不會違背不變性條件或后驗條件。在線程安全類的對象實例上執(zhí)行的任何串行或并行操作都不會使對象處于無效狀態(tài)轰传。

? ? ? ? 在線程安全類中封裝了必要的同步機制驴党,因此客戶端無須進一步采取同步措施。無狀態(tài)對象一定是線程安全的获茬。

原子性

? ? ? ? 在Java中鼻弧,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷锦茁,要么執(zhí)行攘轩,要么不執(zhí)行。 ? ? ? ?

競態(tài)條件

? ??????當某個計算的正確性取決于多個線程的交替執(zhí)行時序時码俩,那么就會發(fā)生競態(tài)條件度帮。換句話說,就是正確的結果要取決于運氣稿存。最常見的競態(tài)條件類型就是 “先檢查后執(zhí)行 (Check-Then-Act)" 操作笨篷, 即通過一個可能失效的觀測結果來決定下一步的動作。

復合操作

? ??????要避免競態(tài)條件問題瓣履,就必須在某個線程修改該變量時率翅,通過某種方式防止其他線程 使用這個變量,從而確保其他線程只能在修改操作完成之前或之后讀取和修改狀態(tài)袖迎, 而不是在修改狀態(tài)的過程中冕臭。

? ??????在java.util.concurrent.atomic包中包含了一些原子變量類,用于實現(xiàn)在數(shù)值和對象引用上的原子狀態(tài)轉換燕锥。通過用AtomicLong來代替long類型的計數(shù)器辜贵,能夠確保所有對計數(shù)器狀態(tài)的訪問操作都是原子的。

????????我們在因數(shù)分解的Servlet中增加了一個計數(shù)器归形,并通過使用線程安全類AtomicLong來管理計數(shù)器的狀態(tài)托慨,從而確保了代碼的線程安全性。當在無狀態(tài)的類中添加一個狀態(tài)時暇榴,如果該狀態(tài)完全由線程安全的對象來管理厚棵,那么這個類仍然是線程安全的蕉世。然而,在2.3節(jié)你將看到婆硬,當狀態(tài)變量的數(shù)量由一個變?yōu)槎鄠€時讨彼,并不會像狀態(tài)變量數(shù)量由零個變?yōu)橐粋€那樣簡單。

????????在實際情況中柿祈,應盡可能地使用現(xiàn)有的線程安全對象(例如AcomicLong)來管理類的狀態(tài)哈误。與非線程安全的對象相比,判斷線程安全對象的可能狀態(tài)及其狀態(tài)轉換情況要更為容易躏嚎,從而也更容易維護和驗證線程安全性蜜自。

加鎖機制



????????在線程安全性的定義中要求, 多個線程之間的操作無論采用何種執(zhí)行時序或交替方式卢佣, 都要保證不變性條件不被破壞重荠。 UnsafeCachingFactorizer 的不變性條件之一是:在 lastFactors 中 緩存的因數(shù)之積應該等于lastNumber 中緩存的數(shù)值。 只有確保了這個不變性條件不被破壞虚茶, 上面的 Servlet 才是正確的戈鲁。 當在不變性條件中涉及多個變量時, 各個變最之間并不是彼此獨立的嘹叫,而是某個變量的值會對其他變量的值產生約束婆殿。 因此,當更新某一個變量在同一 個原子操作中對其他變量同時進行更新罩扇。

????????在某些執(zhí)行時序中婆芦,UnsafeCachingFactorizer可能會破壞這個不變性條件。在使用原子引用的情況下喂饥,盡管對set方法的每次調用都是原子的消约,但仍然無法同時更新lastNumber和 lastFactors。如果只修改了其中一個變量员帮,那么在這兩次修改操作之間或粮,其他線程將發(fā)現(xiàn)不變性 條件被破壞了。同樣捞高,我們也不能保證會同時獲取兩個值:在線程A獲取這兩個值的過程中氯材,線程B可能修改了它們,這樣線程A也會發(fā)現(xiàn)不變性條件被破壞了棠枉。

? ? ? ? 要保持狀態(tài)的一致性浓体,就需要在單個原子操作中更新所有相關的狀態(tài)變量泡挺。

內置鎖

? ??????Java提供了一種內置的鎖機制來支持原子性:同步代碼塊(SynchronizedBlock)辈讶。同步代碼塊包括兩部分: 一個作為鎖的對象引用,一個作為由這個鎖保護的代碼塊娄猫。以關鍵字synchronized來修飾的方法就是一種橫跨整個方法體的同步代碼塊贱除,其中該同步代碼塊的鎖就是方法調用所在的對象生闲。靜態(tài)的synchronized方法以Class對象作為鎖。

synchronized (lock) {

//訪問或修改由鎖保護的共享狀態(tài)

}

????????每個Java對象都可以用做一個實現(xiàn)同步的鎖月幌,這些鎖被稱為內置鎖(Intrinsic Lock) 或監(jiān)視器鎖(Monitor Lock)碍讯。線程在進入同步代碼塊之前會自動獲得鎖,并且在退出同步代碼塊時自動釋放鎖扯躺,而無論是通過正常的控制路徑退出捉兴,還是通過從代碼塊中拋出異常退出。獲得內置鎖的唯一途徑就是進入由這個鎖保護的同步代碼塊或方法录语。

????????Java的內置鎖相當于一種互斥體(或互斥鎖)倍啥,這意味著最多只有一個線程能持有這種鎖。當線程A嘗試獲取一個由線程B持有的鎖時澎埠,線程A必須等待或者阻塞虽缕,直到線程B釋放這個鎖。如果B永遠不釋放鎖蒲稳,那么A也將永遠地等下去氮趋。

????????由于每次只能有一個線程執(zhí)行內置鎖保護的代碼塊,因此江耀,由這個鎖保護的同步代碼塊會以原子方式執(zhí)行剩胁,多個線程在執(zhí)行該代碼塊時也不會相互干擾。并發(fā)環(huán)境中的原子性與事務應用程序中的原子性有著相同的含義一一組語句作為一個不可分割的單元被執(zhí)行祥国。任何一個執(zhí)行同步代碼塊的線程摧冀,都不可能看到有其他線程正在執(zhí)行由同一個鎖保護的同步代碼塊。

????????這種同步機制使得要確保因數(shù)分解Servlet的線程安全性變得更簡單系宫。在程序清單2-6中使用了關鍵字synchronized來修飾service方法索昂,因此在同一時刻只有一個線程可以執(zhí)行service方法。現(xiàn)在的SynchronizedFactorizer是線程安全的扩借。然而椒惨,這種方法卻過于極端,因為多個客戶端無法同時使用因數(shù)分解Servlet, 服務的響應性非常低潮罪,無法令人接受康谆。這是一個性能問題,而不是線程安全問題嫉到,我們將在2.5節(jié)解決這個問題沃暗。

? ??????


重入

? ??????當某個線程請求一個由其他線程持有的鎖時,發(fā)出請求的線程就會阻塞何恶。然而孽锥,由于內置鎖是可重入的,因此如果某個線程試圖獲得一個已經(jīng)由它自己持有的鎖,那么這個請求就會成功惜辑。 “ 重入 ” 意味著獲取鎖的操作的粒度是 “線程”唬涧,而不是 “調用"。重入的一種實現(xiàn)方法是盛撑,為每個鎖關聯(lián)一個獲取計數(shù)值和一個所有者線程碎节。 當計數(shù)值為0時,這個鎖就被認為是沒有被任何線程持有抵卫。 當線程請求一個未被持有的鎖時狮荔,JVM將記下鎖的持有者,并且將獲取計數(shù)值置為1介粘。 如果同一個線程再次獲取這個鎖轴合,計數(shù)值將遞增,而當線程退出同步代碼塊時碗短, 計數(shù)器會相應地遞減受葛。 當計數(shù)值為0時,這個鎖將被釋放.

????????重入進一步提升了加鎖行為的封裝性偎谁,因此簡化了面向對象并發(fā)代碼的開發(fā)总滩。 在程序清單 2-7的代碼中, 子類改寫了父類的synchronized方法巡雨, 然后調用父類中的方法闰渔,此時如果沒有 可重入的鎖,那么這段代碼將產生死鎖铐望。 由于Widget和LoggingWidget中 doSomething方法都是synchronized方法冈涧,因此每個doSomething方法在執(zhí)行前都會獲取Widget上的鎖。 然而正蛙,如果內置鎖不是可重入的督弓,那么在調用super.doSomething時將無法獲得Widget上的鎖, 因為這個鎖已經(jīng)被持有乒验,從而線程將永遠停頓下去愚隧,等待一個永遠也無法獲得的鎖。 重入則避免了這 種死鎖情況的發(fā)生锻全。


用鎖來保護狀態(tài)

? ??????由于鎖能使其保護的代碼路徑以串行形式來訪問狂塘,因此可以通過鎖來構造一些協(xié)議以實現(xiàn)對共享狀態(tài)的獨占訪問。只要始終遵循這些協(xié)議鳄厌,就能確保狀態(tài)的一致性荞胡。

????????訪問共享狀態(tài)的復合操作,例如命中計數(shù)器的遞增操作(讀攘撕俊- 修改- 寫入)或者延遲初始化(先檢查后執(zhí)行)泪漂,都必須是原子操作以避免產生競態(tài)條件。如果在復合操作的執(zhí)行過程中持有一個鎖,那么會使復合操作成為原子操作窖梁。然而赘风,僅僅將復合操作封裝到一個同步代碼塊中是不夠的夹囚。如果用同步來協(xié)調對某個變量的訪問纵刘,那么在訪問這個變量的所有位置上都需要使用同步。而且荸哟,當使用鎖來協(xié)調對某個變量的訪問時假哎,在訪問變量的所有位置上都要使用同一個鎖。

????????一種常見的錯誤是認為鞍历,只有在寫入共享變量時才需要使用同步舵抹,然而事實井非如此。對于可能被多個線程同時訪問的可變狀態(tài)變量劣砍,在訪問它時都需要持有同一個鎖惧蛹,在這種情況下,我們稱狀態(tài)變量是由這個鎖保護的刑枝。

? ? ? ? 對象的內置鎖與其狀態(tài)之間沒有內在的關聯(lián)香嗓。雖然大多數(shù)類都將內置鎖用做一種有效的加鎖機制,但對象的域并不一定要通過內置鎖來保護装畅。當獲取與對象關聯(lián)的鎖時靠娱,并不能阻止其他線程訪問該對象,某個線程在獲得對象的鎖之后掠兄,只能阻止其他線程獲得同一個鎖像云。之所以每個對象都有一個內置鎖,只是為了免去顯式地創(chuàng)建鎖對象蚂夕。你需要自行構造加鎖協(xié)議或者同步策略來實現(xiàn)對共享狀態(tài)的安全訪問迅诬,并且在程序中自始至終地使用它們。

? ??????一種常見的加鎖約定是婿牍,將所有的可變狀態(tài)都封裝在對象內部百框,并通過對象的內置鎖對所有訪問可變狀態(tài)的代碼路徑進行同步,使得在該對象上不會發(fā)生并發(fā)訪問牍汹。在許多線程安全類中都使用了這種模式铐维,例如Vector和其他的同步集合類。在這種情況下慎菲,對象狀態(tài)中的所有變量都由對象的內置鎖保護起來嫁蛇。然而,這種模式并沒有任何特殊之處露该,編譯器或運行時都不會強制實施這種(或者其他的) 模式睬棚。如果在添加新的方法或代碼路徑時忘記了使用同步,那么這種加鎖協(xié)議會很容易被破壞。

? ??????并非所有數(shù)據(jù)都需要鎖的保護抑党, 只有被多個線程同時訪問的可變數(shù)據(jù)才需要通過鎖來保護包警。 第 1 章曾介紹, 當添加一個簡單的異步事件時底靠, 例如 TimerTask, 整個程序都需要滿足線 程安全性要求害晦, 尤其是當程序狀態(tài)的封裝性比較糟糕時∈钪校考慮一個處理大規(guī)模數(shù)據(jù)的單線程程 序壹瘟, 由于任何數(shù)據(jù)都不會在多個線程之間共享, 因此在單線程程序中不需要同步鳄逾。 現(xiàn)在稻轨,假設希望添加一個新功能, 即定期地對數(shù)據(jù)處理進度生成快照雕凹, 這樣當程序崩潰或者必須停止時無 須再次從頭開始殴俱。你可能會選擇使用 TimerTask, 每十分鐘觸發(fā)一次, 并將程序狀態(tài)保存到個文件中枚抵。

????????由于 TimerTask 在另一個(由 Timer 管理的) 線程中調用线欲, 因此現(xiàn)在就有兩個線程同時 訪問快照中的數(shù)據(jù) :程序的主線程與 Timer 線程。 這意味著俄精, 當訪問程序的狀態(tài)時询筏, 不僅TimerTask 代碼必須使用同步, 而且程序中所有訪問相同數(shù)據(jù)的代碼路徑也必須使用同步竖慧。 原本在程序中不需要使用同步嫌套, 現(xiàn)在變成了在程序的各個位置都需要使用同步

????????當某個變量由鎖來保護時圾旨, 意味著在每次訪問這個變量時都需要首先獲得鎖踱讨, 這樣就確保在同一時刻只有一個線程可以訪問這個變量。當類的不變性條件涉及多個狀態(tài)變量時砍的, 那么還 有另外一個需求 :在不變性條件中的每個變量都必須由同一個鎖來保護痹筛。因此可以在單個原子操作中訪間或更新這些變量, 從而確保不變性條件不被破壞廓鞠。 在 SynchronizedFactorizer 類中說明了這條規(guī)則:緩存的數(shù)值和因數(shù)分解結果都由 Servlet 對象的內置鎖來保護帚稠。

? ? ? ? 對于每個包含多個變量的不變性條件,其中設計的所有變量都需要由同一個鎖保護床佳。

? ??????如果同步可以避免競態(tài)條件問題滋早, 那么為什么不在每個方法聲明時都使用關鍵synchronized ? 事實上, 如果不加區(qū)別地濫用synchronized, 可能導致程序中出現(xiàn)過多的同步砌们。此外杆麸,如果只是將每個方法都作為同步方法搁进, 例如Vector, 那么并不足以確保Vector上復合操作都是原子的


????????雖然contains和add 等方法都是原子方法, 但在上面這個“ 如果不存在則添加(put-ifabsent)"的操作中仍然存在競態(tài)條件昔头。雖然synchronized 方法可以確保單個操作的原子性饼问, 但如果要把多個操作合并為一個復合操作, 還是需要額外的加鎖機制(請參見4.4 節(jié)了解如何在線程安全對象中添加原子操作的方法)揭斧。此外莱革, 將每個方法都作為同步方法還可能導致活躍性問題(Liveness) 或性能問題(Performance), 我們在SynchronizedFactorizer 中已經(jīng)看到了這些問題。

活躍性與性能

? ??????在UnsafeCachingFactorizer 中未蝌, 我們通過在因數(shù)分解Servlet 中引入了緩存機制來提升性能驮吱。在緩存中需要使用共享狀態(tài)茧妒, 因此需要通過同步來維護狀態(tài)的完整性萧吠。然而, 如果使用SynchronizedFactorizer 中的同步方式桐筏, 那么代碼的執(zhí)行性能將非常糟糕

????????SynchronizedFactorizer中采用的同步策略是纸型,通過Servlet 對象的內置鎖來保護每一個狀態(tài)變量, 該策略的實現(xiàn)方式也就是對整個 service 方法進行同步梅忌。雖然這種簡單且粗粒度的方法能確保線程安全性狰腌,但付出的代價卻很高。

????????由于 service 是一個 synchronized方法牧氮,因此每次只有一個線程可以執(zhí)行琼腔。這就背離了 Serlvet 框架的初衷,即 Serlvet 需要能同時處理多個請求踱葛,這在負載過高的情況下將給用戶帶來糟糕的體驗丹莲。如果 Servlet 在對某個大數(shù)值進行因數(shù)分解時需要很長的執(zhí)行時間,那么其他的客戶端必須一直等待尸诽,直到 Servlet 處理完當前的請求甥材, 才能開始另一個新的因數(shù)分解運算。如 果在系統(tǒng)中有多個CPU 系統(tǒng)性含,那么當 負載很高時洲赵,仍然會有處理器處千空閑狀態(tài)。即使一些執(zhí) 行時間很短的請求商蕴,比如訪問緩存的值叠萍,仍然需要很長時間, 因為這些請求都必須等待前一個 請求執(zhí)行完成绪商。

????????圖 2-1 給出了當多個請求同時到達因數(shù)分解 Servlet 時發(fā)生的情況:這些請求將排隊等待處理苛谷。我們將這種 Web 應用程序稱之為不良并發(fā) (Poor Concurrency) 應用程序可同時調用的數(shù)量,不僅受到可用處理資源的限制部宿, 還受到應用程序本身結構的限制抄腔。幸運的是瓢湃,通過縮小同步代碼塊的作用范圍,我們很容易做到既確保 Servlet 的并發(fā)性赫蛇,同時又維護線程安全性绵患。要確保同步代碼塊不要過小,并且不要將本應是原子的操作拆分到多個同步代碼塊中悟耘。應該盡量將不影響共享狀態(tài)且執(zhí)行時間較長的操作從同步代碼塊中分離出去落蝙,從而在這些操作的執(zhí)行過程中,其他線程可以訪問共享狀態(tài)暂幼。


????????當使用鎖時筏勒, 你應該清楚代碼塊中實現(xiàn)的功能, 以及在執(zhí)行該代碼塊時是否需要很長的時 間旺嬉。 無論是執(zhí)行計算密集的操作管行, 還是在執(zhí)行某個可能阻塞的操作, 如果持有鎖的時間過長邪媳, 那么都會帶來活躍性或性能問題捐顷。

? ? ? ? 當執(zhí)行時間較長的計算或者可能無法快速完成的操作時(如網(wǎng)絡I/o或控制臺I/O),一定不要持有鎖雨效。

? ??????


? ??????

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末迅涮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子徽龟,更是在濱河造成了極大的恐慌叮姑,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件据悔,死亡現(xiàn)場離奇詭異传透,居然都是意外死亡,警方通過查閱死者的電腦和手機屠尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門旷祸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讼昆,你說我怎么就攤上這事托享。” “怎么了浸赫?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵闰围,是天一觀的道長。 經(jīng)常有香客問我既峡,道長羡榴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任运敢,我火速辦了婚禮校仑,結果婚禮上忠售,老公的妹妹穿的比我還像新娘。我一直安慰自己迄沫,他們只是感情好稻扬,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著羊瘩,像睡著了一般泰佳。 火紅的嫁衣襯著肌膚如雪悔橄。 梳的紋絲不亂的頭發(fā)上曹宴,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天惜论,我揣著相機與錄音祝懂,去河邊找鬼。 笑死髓堪,一個胖子當著我的面吹牛榔组,可吹牛的內容都是我干的卷玉。 我是一名探鬼主播侧戴,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼宁昭,長吁一口氣:“原來是場噩夢啊……” “哼跌宛!你這毒婦竟也來了酗宋?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤疆拘,失蹤者是張志新(化名)和其女友劉穎蜕猫,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哎迄,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡回右,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了漱挚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔烁。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖旨涝,靈堂內的尸體忽然破棺而出蹬屹,到底是詐尸還是另有隱情,我是刑警寧澤白华,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布慨默,位于F島的核電站,受9級特大地震影響弧腥,放射性物質發(fā)生泄漏厦取。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一管搪、第九天 我趴在偏房一處隱蔽的房頂上張望虾攻。 院中可真熱鬧铡买,春花似錦、人聲如沸霎箍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朋沮。三九已至蛇券,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樊拓,已是汗流浹背纠亚。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筋夏,地道東北人蒂胞。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像条篷,于是被迫代替她去往敵國和親骗随。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容