溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java并發(fā)和線程安全怎么掌握

發(fā)布時(shí)間:2022-01-12 21:44:28 來(lái)源:億速云 閱讀:133 作者:iii 欄目:編程語(yǔ)言

本文小編為大家詳細(xì)介紹“Java并發(fā)和線程安全怎么掌握”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Java并發(fā)和線程安全怎么掌握”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

為什么有多線程

談到多線程,我們很容易與高性能畫上等號(hào),但是并非如此,舉個(gè)簡(jiǎn)單的例子,從1加到100,用四個(gè)線程計(jì)算不一定比一個(gè)線程來(lái)得快。因?yàn)榫€程的創(chuàng)建和上下文切換,是一筆巨大的開銷。

那么設(shè)計(jì)多線程的初衷是什么呢?來(lái)看一個(gè)這樣的實(shí)際例子,計(jì)算機(jī)通常需要與人來(lái)交互,假設(shè)計(jì)算機(jī)只有一個(gè)線程,并且這個(gè)線程在等待用戶的輸入,那么在等待的過程中,CPU什么事情也做不了,只能等待,造成CPU的利用率很低。如果設(shè)計(jì)成多線程,在CPU在等待資源的過程中,可以切到其他的線程上去,提高CPU利用率。

現(xiàn)代處理器大多含有多個(gè)CPU核心,那么對(duì)于運(yùn)算量大任務(wù),可以用多線程的方式拆解成多個(gè)小任務(wù)并發(fā)的執(zhí)行,提高計(jì)算的效率。

總結(jié)起來(lái)無(wú)非兩點(diǎn),提高CPU的利用率、提高計(jì)算效率。

線程安全的本質(zhì)

我們先來(lái)看一個(gè)例子:

Java并發(fā)和線程安全怎么掌握

上面是一個(gè)把變量自增100次的例子,只不過用了4個(gè)線程,每個(gè)線程自增25次,用CountDownLatch等4個(gè)線程執(zhí)行完,打印出最終結(jié)果。實(shí)際上,我們希望程序的結(jié)果是100,但是打印出來(lái)的結(jié)果并非總是100。

這就引出了線程安全所描述的問題,我們先用通俗的話來(lái)描述一下線程安全:

線程安全就是要讓程序運(yùn)行出我們想要的結(jié)果,或者話句話說,讓程序像我們看到的那樣執(zhí)行。

解釋一下我總結(jié)的這句話,我們先new出了一個(gè)add對(duì)象,調(diào)用了對(duì)象的doAdd方法,本來(lái)我們希望每個(gè)線程有序的自增25次,最終得到正確的結(jié)果。如果程序增的像我們預(yù)先設(shè)定的那樣運(yùn)行,那么這個(gè)對(duì)象就是線程安全的。

下面我們來(lái)看看Brian Goetz對(duì)線程安全的描述:當(dāng)多線程訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那么這個(gè)對(duì)象就是線程安全的。

下面我們就來(lái)分析這段代碼為什么不能確??偸堑玫秸_的結(jié)果。

Java內(nèi)存模型(JMM)數(shù)據(jù)可見性問題、指令重排序、內(nèi)存屏障

先從計(jì)算機(jī)的硬件效率說起,CPU的計(jì)算速度比內(nèi)存快幾個(gè)數(shù)量級(jí),為了平衡CPU和內(nèi)存之間的矛盾,引入的高速緩存,每個(gè)CPU都有高速緩存,甚至是多級(jí)緩存L1、L2和L3,那么緩存與內(nèi)存的交互需要緩存一致性協(xié)議,這里就不深入講解。那么最終處理器、高速緩存、主內(nèi)存的交互關(guān)系如下:

Java并發(fā)和線程安全怎么掌握

那么Java的內(nèi)存模型(Java Memory Model,簡(jiǎn)稱JMM)也定義了線程、工作內(nèi)存、主內(nèi)存之間的關(guān)系,非常類似于硬件方面的定義。

這里順帶提一下,Java虛擬機(jī)運(yùn)行時(shí)內(nèi)存的區(qū)域劃分

·方法區(qū):存儲(chǔ)類信息、常量、靜態(tài)變量等,各線程共享

·虛擬機(jī)棧:每個(gè)方法的執(zhí)行都會(huì)創(chuàng)建棧幀,用于存儲(chǔ)局部變量、操作數(shù)棧、動(dòng)態(tài)鏈接等,虛擬機(jī)棧主要存儲(chǔ)這些信息,線程私有

·本地方法棧:虛擬機(jī)使用到的Native方法服務(wù),例如c程序等,線程私有

·程序計(jì)數(shù)器:記錄程序運(yùn)行到哪一行了,相當(dāng)于當(dāng)前線程字節(jié)碼的行號(hào)計(jì)數(shù)器,線程私有

·堆:new出的實(shí)例對(duì)象都存儲(chǔ)在這個(gè)區(qū)域,是GC的主戰(zhàn)場(chǎng),線程共享。

所以對(duì)于JMM定義的主內(nèi)存,大部分時(shí)候可以對(duì)應(yīng)堆內(nèi)存、方法區(qū)等線程共享的區(qū)域,這里只是概念上對(duì)應(yīng),其實(shí)程序計(jì)數(shù)器、虛擬機(jī)棧等也有部分是放在主內(nèi)存的,具體看虛擬機(jī)的設(shè)計(jì)。

好了,了解了JMM內(nèi)存模型,我們來(lái)分析一下,上面的程序?yàn)槭裁礇]得到正確的結(jié)果。請(qǐng)看下圖,線程A、B同時(shí)去讀取主內(nèi)存的count初始值存放在各自的工作內(nèi)存里,同時(shí)執(zhí)行了自增操作,寫回主內(nèi)存,最終得到了錯(cuò)誤的結(jié)果。

 我們?cè)賮?lái)深入分析一下,造成這個(gè)錯(cuò)誤的本質(zhì)原因:

·可見性,工作內(nèi)存的最新值不知道什么時(shí)候會(huì)寫回主內(nèi)存

·有序性,線程之間必須是有序的訪問共享變量,我們用“視界”這個(gè)概念來(lái)描述一下這個(gè)過程,以B線程的視角看,當(dāng)他看到A線程運(yùn)算好之后,把值寫回之內(nèi)存之后,馬上去讀取最新的值來(lái)做運(yùn)算。A線程也應(yīng)該是看到B運(yùn)算完之后,馬上去讀取,在做運(yùn)算,這樣就得到了正確的結(jié)果。

接下來(lái),我們來(lái)具體分析一下,為什么要從可見性和有序性兩個(gè)方面來(lái)限定。

給count加上volatile關(guān)鍵字,就保證了可見性。

private volatile int count = 0;

volatile關(guān)鍵字,會(huì)在最終編譯出來(lái)的指令上加上lock前綴,lock前綴的指令做三件事情

·防止指令重排序(這里對(duì)本問題的分析不重要,后面會(huì)詳細(xì)來(lái)講)

·鎖住總線或者使用鎖定緩存來(lái)保證執(zhí)行的原子性,早期的處理可能用鎖定總線的方式,這樣其他處理器沒辦法通過總線訪問內(nèi)存,開銷比較大,現(xiàn)在的處理器都是用鎖定緩存的方式,在配合緩存一致性來(lái)解決。

·把緩沖區(qū)的所有數(shù)據(jù)都寫回主內(nèi)存,并保證其他處理器緩存的該變量失效

既然保證了可見性,加上了volatile關(guān)鍵詞,為什么還是無(wú)法得到正確的結(jié)果,原因是count++,并非原子操作,count++等效于如下步驟:

·從主內(nèi)存中讀取count賦值給線程副本變量:temp=count

·線程副本變量加1:temp=temp+1

·線程副本變量寫回主內(nèi)存:count=temp

就算是真的嚴(yán)苛的給總線加鎖,導(dǎo)致同一時(shí)刻,只能有一個(gè)處理器訪問到count變量,但是在執(zhí)行第(2)步操作時(shí),其他cpu已經(jīng)可以訪問count變量,此時(shí)最新運(yùn)算結(jié)果還沒刷回主內(nèi)存,造成了錯(cuò)誤的結(jié)果,所以必須保證順序性。

那么保證順序性的本質(zhì),就是保證同一時(shí)刻只有一個(gè)CPU可以執(zhí)行臨界區(qū)代碼。這時(shí)候做法通常是加鎖,鎖本質(zhì)是分兩種:悲觀鎖和樂觀鎖。如典型的悲觀鎖synchronized、JUC包下面典型的樂觀鎖ReentrantLock。

讀到這里,這篇“Java并發(fā)和線程安全怎么掌握”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI