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