Java傳統(tǒng)編程模型存在的問(wèn)題

2021-02-27    分類: 網(wǎng)站建設(shè)

Actor模型不僅僅被認(rèn)為是一種高效的解決方案 ,它已經(jīng)在世界上一些要求最苛刻的應(yīng)用中得到了驗(yàn)證,為了突出Actor模型所解決的問(wèn)題,本節(jié)首先討論傳統(tǒng)編程模型與現(xiàn)代多線程和多CPU的硬件架構(gòu)之間的不匹配:

  • 對(duì)面向?qū)ο笾蟹庋b(encapsulation)特性的挑戰(zhàn)
  • 對(duì)共享內(nèi)存在現(xiàn)代計(jì)算機(jī)架構(gòu)上的誤解
  • 對(duì)調(diào)用堆棧的誤解

對(duì)封裝特性的挑戰(zhàn)

封裝(encapsulation)是面向?qū)ο缶幊?OOP)中的一大特性,封裝意味著對(duì)象內(nèi)部的數(shù)據(jù)不能夠在對(duì)象外直接訪問(wèn),必須通過(guò)對(duì)象提供的一系列方法來(lái)間接進(jìn)行訪問(wèn)。對(duì)象負(fù)責(zé)公開對(duì)數(shù)據(jù)的安全操作的方法,以保護(hù)其封裝的數(shù)據(jù)的不變性。

在多線程下,多個(gè)線程同時(shí)調(diào)用同一個(gè)對(duì)象的方法來(lái)修改其內(nèi)部封裝的數(shù)據(jù)時(shí)候,就會(huì)存在線程安全問(wèn)題,這是因?yàn)榉庋b本身不確保對(duì)象內(nèi)部數(shù)據(jù)的一致性,在不對(duì)對(duì)象的方法在修改數(shù)據(jù)施加一定同步措施時(shí),對(duì)象內(nèi)的數(shù)據(jù)就會(huì)在多線程訪問(wèn)下變得不確定了。

一個(gè)解決該問(wèn)題的方式就是,多線程訪問(wèn)對(duì)象方法內(nèi)數(shù)據(jù)時(shí)候施加一定同步措施,比如加鎖,加鎖可以保證同時(shí)只有一個(gè)線程可以訪問(wèn)對(duì)象內(nèi)的數(shù)據(jù),但是加鎖會(huì)帶來(lái)昂貴的代價(jià):

  • 使用鎖會(huì)嚴(yán)重影響并發(fā)度,使用鎖在現(xiàn)在CPU架構(gòu)中是一個(gè)比較昂貴的操作,因?yàn)楫?dāng)線程獲取鎖失敗后會(huì)把線程從用戶態(tài)切換到內(nèi)核態(tài)把線程掛起,稍后喚醒后又需要從內(nèi)核態(tài)切換到用戶態(tài)進(jìn)行運(yùn)行。
  • 獲取鎖失敗的調(diào)用線程會(huì)被阻塞掛起,因此它不能做任何有意義的事情。即使在桌面應(yīng)用程序中這也是不可取的,我們想要的是即使后臺(tái)有一個(gè)運(yùn)行比較耗時(shí)的工作在運(yùn)行,也要保證系統(tǒng)對(duì)用戶的一部分請(qǐng)求有響應(yīng)。在后端應(yīng)用中,阻塞是完全浪費(fèi)資源的。另外可能有人認(rèn)為雖然當(dāng)前線程阻塞了,但是我們可以通過(guò)啟動(dòng)新線程來(lái)彌補(bǔ)這一點(diǎn),但是需要注意一點(diǎn),線程也是一種昂貴的資源,操作系統(tǒng)對(duì)線程個(gè)數(shù)是有限制的。
  • 另外鎖的存在,帶來(lái)了新的威脅,即死鎖問(wèn)題的存在。

由于以上問(wèn)題的存在,導(dǎo)致我們進(jìn)退兩難:

  • 如果不使用足夠的鎖,則不能保證多線程下對(duì)象中數(shù)據(jù)不受到破壞。
  • 如果在對(duì)象中每個(gè)數(shù)據(jù)訪問(wèn)是都加了鎖,則會(huì)導(dǎo)致系統(tǒng)性能下降,并且很容易導(dǎo)致死鎖的發(fā)生。

另外,鎖只能在單JVM內(nèi)(本地鎖)很好的工作。當(dāng)涉及到跨多臺(tái)機(jī)協(xié)調(diào)時(shí),只能使用分布式鎖。但是分布式鎖的效率比本地鎖低幾個(gè)數(shù)量級(jí),這是因?yàn)榉植际芥i協(xié)議需要跨多臺(tái)機(jī)在網(wǎng)絡(luò)上進(jìn)行多次往返通信,所以其造成較大的影響就是延遲。

小結(jié):

  • 對(duì)象只能在單線程情況下保證封裝的安全性,也就是保證對(duì)象封裝的數(shù)據(jù)的線程安全性。多線程下修改對(duì)象內(nèi)的數(shù)據(jù)大多情況下會(huì)導(dǎo)致數(shù)據(jù)被污染,造成臟數(shù)據(jù)產(chǎn)生。在同一代碼段中有兩個(gè)競(jìng)爭(zhēng)線程會(huì)導(dǎo)致違反每個(gè)不變式。
  • 雖然鎖看起來(lái)是保證多線程下封特性比較直接的方式,但實(shí)際上使用鎖的效率低下,并且在任何實(shí)際規(guī)模的應(yīng)用中都容易導(dǎo)致死鎖的產(chǎn)生。
  • 本地鎖是我們經(jīng)常使用的,但是如果嘗試將其擴(kuò)展為分布式鎖,則是有代價(jià)的,并且其橫向擴(kuò)展的潛力有限。

對(duì)共享內(nèi)存在現(xiàn)代計(jì)算機(jī)架構(gòu)上的誤解

在80-90年代的編程模型概念化地表示,寫入變量時(shí)候是直接把其值寫入主內(nèi)存里面(這有點(diǎn)混淆了局部變量可能只存在于cpu寄存器中的事實(shí))。在現(xiàn)在計(jì)算機(jī)硬件架構(gòu)中,計(jì)算機(jī)系統(tǒng)中為了解決主內(nèi)存與CPU運(yùn)行速度的差距,在CPU與主內(nèi)存之間添加了一級(jí)或者多級(jí)高速緩沖存儲(chǔ)器(Cache),每個(gè)cache有好多cache行組成,這些Cache一般是集成到CPU內(nèi)部的,所以也叫 CPU Cache。所以當(dāng)我們寫入變量的時(shí)候?qū)嶋H是寫入到了當(dāng)前cpu的Cache中,而不是直接寫入到主內(nèi)存中,并且當(dāng)前cpu核對(duì)自己cache寫入的變量對(duì)其他cpu核是不可見的,這即是Java內(nèi)存模型中共享變量的內(nèi)存不可見問(wèn)題。

在JVM中我們可以把變量使用volatile關(guān)鍵字修飾或者使用JUC包中的原子性變量比如AtomicLong對(duì)普通變量進(jìn)行包裝來(lái)保證多線程下共享變量的內(nèi)存可見性,當(dāng)然使用加鎖的方式也可以保證內(nèi)存可見性,但是其開銷更大。既然使用volatile關(guān)鍵字可以解決共享變量?jī)?nèi)存可見性問(wèn)題,那么為何不把所有變量都使用volatile修飾那?這是因?yàn)槭褂胿olatile修飾變量,寫入該變量的時(shí)候會(huì)把cache直接刷新會(huì)內(nèi)存,讀取時(shí)候會(huì)把cache內(nèi)緩存失效,然后從主內(nèi)存加載數(shù)據(jù),這就破壞了cache的命中率,對(duì)性能是有損的。

所以我們需要細(xì)心的分析哪些變量需要使用volatile修飾,但是即使開發(fā)人員意識(shí)到volatile可以解決內(nèi)存不可見問(wèn)題,但是從系統(tǒng)中找出哪些變量需要使用volatile或者原子性結(jié)構(gòu)進(jìn)行修飾也是一個(gè)困難的事情,這使得我們?cè)诜菢I(yè)務(wù)邏輯處理上需要耗掉一部分精力。

小結(jié):

  • 在現(xiàn)在多核CPU的硬件架構(gòu)中,多線程之間不再有真正的共享內(nèi)存,cpu核心之間顯示傳遞數(shù)據(jù)塊(cache 行)將和網(wǎng)絡(luò)中不同計(jì)算機(jī)之間傳遞數(shù)據(jù)一樣。 CPU核心之間通信和網(wǎng)絡(luò)通信的共同點(diǎn)比許多人意識(shí)到的要多?,F(xiàn)在跨CPU或聯(lián)網(wǎng)計(jì)算機(jī)傳遞消息已成為一種規(guī)范。
  • 除了通過(guò)使用volatile修飾共享的變量或使用原子數(shù)據(jù)結(jié)構(gòu)保證共享變量?jī)?nèi)存可見性之外,一個(gè)更嚴(yán)格和原則上的方法是將狀態(tài)保持在并發(fā)實(shí)體本地,并通過(guò)消息顯式在并發(fā)實(shí)體之間傳播數(shù)據(jù)或事件。

對(duì)調(diào)用堆棧的誤解

提起調(diào)用堆棧( Call stacks)大家都耳熟能詳,但是其被發(fā)明是在并發(fā)編程不是那么重要時(shí)候,那時(shí)候多核cpu系統(tǒng)還不常見,所以調(diào)用堆棧不會(huì)跨線程,因此不會(huì)為異步調(diào)用鏈提供調(diào)用堆棧能力。

在多線程下,當(dāng)主線程(調(diào)用線程)開啟一個(gè)異步線程運(yùn)行異步任務(wù)時(shí)候,問(wèn)題就出現(xiàn)了。這時(shí)候主線程會(huì)將共享對(duì)象放到異步線程可以訪問(wèn)到的共享內(nèi)存里面,然后開啟異步線程后主線程繼續(xù)做自己的事情,而異步線程則會(huì)從共享內(nèi)存中訪問(wèn)到主線程創(chuàng)建的共享對(duì)象,然后進(jìn)行異步處理。

進(jìn)行異步處理時(shí)候遇到的第一個(gè)問(wèn)題是當(dāng)異步線程執(zhí)行完畢任務(wù)后,如何通知主線程?另外當(dāng)異步任務(wù)執(zhí)行出現(xiàn)異常時(shí)候該怎么做?這個(gè)異常將會(huì)被異步線程捕獲,并且不會(huì)傳遞給主調(diào)用線程。

理論上主調(diào)用線程需要在異步任務(wù)執(zhí)行完畢或者出異常時(shí)候被通知,但是沒(méi)有調(diào)用堆??梢詡鬟f異常。異步任務(wù)執(zhí)行失敗的的通知只能通過(guò)輔助方式完成,比如Future方式,將錯(cuò)誤碼寫到主調(diào)用線程所在的地方,否則一旦準(zhǔn)備好就希望得到結(jié)果。如果沒(méi)有此通知,則主調(diào)用線程將永遠(yuǎn)不會(huì)收到失敗通知,并且任務(wù)將丟失!這類似于網(wǎng)絡(luò)系統(tǒng)的工作方式,其中消息/請(qǐng)求可能會(huì)丟失/失敗而不會(huì)發(fā)出任何通知。

當(dāng)真的發(fā)生錯(cuò)誤時(shí),這種情況會(huì)變得更糟,當(dāng)異步工作線程遇到錯(cuò)誤時(shí)候會(huì)導(dǎo)致最終陷入無(wú)法恢復(fù)的境地。例如由錯(cuò)誤引起的內(nèi)部異常會(huì)冒泡到線程的根,并使線程關(guān)閉。這立即引發(fā)了一個(gè)問(wèn)題,誰(shuí)應(yīng)該重新啟動(dòng)該異步線程執(zhí)行的任務(wù),以及如何將其還原到已知狀態(tài)?乍一看,這似乎是可以管理的,但我們突然遇到了一個(gè)新的現(xiàn)象:異步線程當(dāng)前正在執(zhí)行的實(shí)際任務(wù)我們并沒(méi)有存放起來(lái)。實(shí)際上,由于到達(dá)頂部的異常使所有調(diào)用棧退出,任務(wù)狀態(tài)已經(jīng)完全丟失了!即使這是本地通信,也沒(méi)有網(wǎng)絡(luò)連接,但是我們還是丟失了一條消息(可能會(huì)丟失消息)。

小結(jié):

為了在當(dāng)前系統(tǒng)上實(shí)現(xiàn)任何有意義的并發(fā)性和提高性能,線程必須以高效的方式在彼此之間委派任務(wù),而不會(huì)阻塞。使用這種類型的任務(wù)委派并發(fā)(甚至在網(wǎng)絡(luò)/分布式計(jì)算中更是如此),基于調(diào)用堆棧的錯(cuò)誤處理會(huì)導(dǎo)致崩潰。因此需要引入新的顯式錯(cuò)誤信令機(jī)制,讓失敗成為域模型的一部分。

具有工作委派的并發(fā)系統(tǒng)需要處理服務(wù)故障,并需要具有從故障中恢復(fù)的原則方法。此類服務(wù)的客戶端需要注意,任務(wù)/消息可能會(huì)在重新啟動(dòng)期間丟失。即使沒(méi)有發(fā)生損失,由于先前排隊(duì)的任務(wù)(較長(zhǎng)的隊(duì)列)或者垃圾回收導(dǎo)致的延遲等,將會(huì)導(dǎo)致響應(yīng)可能會(huì)被任意延遲。面對(duì)這些情況,并發(fā)系統(tǒng)應(yīng)以超時(shí)的形式處理響應(yīng)截止日期。

本文名稱:Java傳統(tǒng)編程模型存在的問(wèn)題
文章起源:http://muchs.cn/news14/103314.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)公司網(wǎng)站設(shè)計(jì)、企業(yè)網(wǎng)站制作、域名注冊(cè)網(wǎng)站內(nèi)鏈、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)