Java并發(fā)編程:什么是線程安全性--基礎知識
??前言
來自我對一位我所崇拜的大佬文章的評論:
我:“喝罷黃河之水天上來,酒醒楊柳殘月且偷歡,唱罷笑傲江湖祭滄海,雁渡寒潭有幾只回還”
大佬:“年少正恰,縱碼飛騁,略江山華月,有幾人隨傅虎踞龍盤。”
??進入正題
線程或者鎖在并發(fā)編程中的作用,類似于鉚釘和工字梁在土木工程中的作用。要建筑一座堅固的橋梁,必須正確地使用大量的鉚釘和工字梁。同理,在構建穩(wěn)健的并發(fā)程序時,必須正確地使用線程和鎖。但這些終歸只是一些機制。要編寫線程安全的代碼,其核心在于要對狀態(tài)訪問操作進行管理,特別是對共享(Shared)和可變的(Mutable)狀態(tài)的訪問。
對象的狀態(tài):指存儲在狀態(tài)變量中的數(shù)據(jù),當然也可能包含其他依賴對象的域。
例如,某個
HashMap的狀態(tài)不僅存儲在HashMap對象本身,還存儲在許多Map.Entry對象中。在對象的狀態(tài)中包含了任何可能影響其外部可見行為的數(shù)據(jù)。共享Shared:共享意味著變量可以有多個線程同時訪問。
可變Mutable:意味著變量的值在其生命周期內(nèi)可以發(fā)生變化。
注釋:討論線程安全性,更應該側(cè)重于如何防止在數(shù)據(jù)上發(fā)生不受控的并發(fā)訪問。一個對象是否需要是線程安全的,取決于他是否被多個線程訪問。(指的是在程序中訪問對象的方式,而不是對象要實現(xiàn)的功能)
當多個線程訪問某個狀態(tài)變量并且其中有一個線程執(zhí)行寫入操作時,必須采用同步機制來協(xié)同這些線程對變量的訪問。
關鍵字
synchronized:提供獨占的加鎖方式。voatile類型變量:同步應該要包括的還有,顯式鎖(Explicit Lock)以及原子變量。
如果忽略了某一個同步的機制,可能會造成的后果,當多個線程訪問同一個可變的狀態(tài)變量時沒有使用合適的同步,程序就會出錯,修復:
不在線程之間共享該狀態(tài)變量
將狀態(tài)變量修改為不可變的變量
在訪問狀態(tài)變量時使用同步
注釋:當設計線程安全的類時,良好的面向?qū)ο蠹夹g、不可修改性,以及明晰的不變形規(guī)范都能起到一定的幫助作用。
在編寫并發(fā)應用程序時,一種正確的編程方法就是:首先代碼正確運行,然后再提高代碼速度。即便如此,最好也只是當性能測試結(jié)果和應用需求告訴你必須提高性能,以及測量結(jié)果表明這種優(yōu)化在實際環(huán)境中確實能帶來性能提升時,才進行優(yōu)化。
?? 什么是線程安全性
我們都知道:定義越正式,就越復雜,不僅很難提供由實際意義的指導建議,而且也很難從直觀上去理解。網(wǎng)上的“定義”有很多,比如:
······ 可以在多個線程中調(diào)用,并且在線程之間不會出現(xiàn)錯誤的交互。
······ 可以同時被多個線程調(diào)用,而調(diào)用者無須執(zhí)行額外的動作。
也難怪我們會對線程安全性感到困惑,因為聽起來就想“如果某個類可以在多個線程中安全的使用,那么它就是一個線程安全的類?!彪m然不存在很多爭議,但是有什么實際的意義和幫助么。
“安全”的含義是什么:
在線程安全的定義中,最核心的概念就是正確性。
正確的含義是——某個類的行為與其規(guī)范完全一致
我將單線程的正確性近似定義為“所見即所知”,在對“正確性”給出了一個較為清晰的定以后,就可以定義線程安全性:
當多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。
當多個線程訪問某個類時,不關于刑事環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱呼這個類是線程安全的。
由于單線程程序也可以看成多線程程序,若某個類在單線程環(huán)境下都不是正確的,那么它肯定不會是線程安全(也就是說一個程序首先要能正常的工作保證正確性)。
?簡單的示例
通常,線程安全性的需求并非來源于對縣城的直接使用,而是使用像 Servlet 這樣的框架(還有很多)。
一個基于 Servlet 的因數(shù)分解服務,并逐步擴展功能,并確保它的線程安全性。
一個無狀態(tài)的 Servlet:
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req,ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp,factors);
}
}
復制代碼與大多數(shù) Servlet 相同,StatelessFactorizer 是無狀態(tài)的,它既不包含任何域,也不包含任何對其他類中域的引用。計算過程中的臨時狀態(tài)僅存在于線程棧上的局部變量中,并且只能由正在執(zhí)行的線程訪問。訪問 StatelessFactorizer 的線程不會影響另外一個訪問同一個 StatelessFactorizer 的線程的計算結(jié)果 ,因為這兩個線程并沒有共享狀態(tài),就好像他們都在訪問不同的實例。由于線程訪問無狀態(tài)對象的行為并不會影響其他線程中操作的正確性,因此無狀態(tài)對象是線程安全的。
無狀態(tài)對象一定是線程安全的。
大多數(shù)
Servlet都是無狀態(tài)的,從而極大地降低了在實現(xiàn)Servlet線程安全性時的復雜性。只有當Servlet在處理請求時需要保存一些信息線程安全性才會成為一個問題。
作者:Sunny_Chen
鏈接:https://juejin.cn/post/6999423430462275592
來源:掘金
著作權歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權,非商業(yè)轉(zhuǎn)載請注明出處。
