2021-02-27 分類: 網(wǎng)站建設(shè)
Actor模型不僅僅被認為是一種高效的解決方案 ,它已經(jīng)在世界上一些要求最苛刻的應(yīng)用中得到了驗證,為了突出Actor模型所解決的問題,本節(jié)首先討論傳統(tǒng)編程模型與現(xiàn)代多線程和多CPU的硬件架構(gòu)之間的不匹配:
對封裝特性的挑戰(zhàn)
封裝(encapsulation)是面向?qū)ο缶幊?OOP)中的一大特性,封裝意味著對象內(nèi)部的數(shù)據(jù)不能夠在對象外直接訪問,必須通過對象提供的一系列方法來間接進行訪問。對象負責公開對數(shù)據(jù)的安全操作的方法,以保護其封裝的數(shù)據(jù)的不變性。
在多線程下,多個線程同時調(diào)用同一個對象的方法來修改其內(nèi)部封裝的數(shù)據(jù)時候,就會存在線程安全問題,這是因為封裝本身不確保對象內(nèi)部數(shù)據(jù)的一致性,在不對對象的方法在修改數(shù)據(jù)施加一定同步措施時,對象內(nèi)的數(shù)據(jù)就會在多線程訪問下變得不確定了。
一個解決該問題的方式就是,多線程訪問對象方法內(nèi)數(shù)據(jù)時候施加一定同步措施,比如加鎖,加鎖可以保證同時只有一個線程可以訪問對象內(nèi)的數(shù)據(jù),但是加鎖會帶來昂貴的代價:
由于以上問題的存在,導(dǎo)致我們進退兩難:
另外,鎖只能在單JVM內(nèi)(本地鎖)很好的工作。當涉及到跨多臺機協(xié)調(diào)時,只能使用分布式鎖。但是分布式鎖的效率比本地鎖低幾個數(shù)量級,這是因為分布式鎖協(xié)議需要跨多臺機在網(wǎng)絡(luò)上進行多次往返通信,所以其造成較大的影響就是延遲。
小結(jié):
對共享內(nèi)存在現(xiàn)代計算機架構(gòu)上的誤解
在80-90年代的編程模型概念化地表示,寫入變量時候是直接把其值寫入主內(nèi)存里面(這有點混淆了局部變量可能只存在于cpu寄存器中的事實)。在現(xiàn)在計算機硬件架構(gòu)中,計算機系統(tǒng)中為了解決主內(nèi)存與CPU運行速度的差距,在CPU與主內(nèi)存之間添加了一級或者多級高速緩沖存儲器(Cache),每個cache有好多cache行組成,這些Cache一般是集成到CPU內(nèi)部的,所以也叫 CPU Cache。所以當我們寫入變量的時候?qū)嶋H是寫入到了當前cpu的Cache中,而不是直接寫入到主內(nèi)存中,并且當前cpu核對自己cache寫入的變量對其他cpu核是不可見的,這即是Java內(nèi)存模型中共享變量的內(nèi)存不可見問題。
在JVM中我們可以把變量使用volatile關(guān)鍵字修飾或者使用JUC包中的原子性變量比如AtomicLong對普通變量進行包裝來保證多線程下共享變量的內(nèi)存可見性,當然使用加鎖的方式也可以保證內(nèi)存可見性,但是其開銷更大。既然使用volatile關(guān)鍵字可以解決共享變量內(nèi)存可見性問題,那么為何不把所有變量都使用volatile修飾那?這是因為使用volatile修飾變量,寫入該變量的時候會把cache直接刷新會內(nèi)存,讀取時候會把cache內(nèi)緩存失效,然后從主內(nèi)存加載數(shù)據(jù),這就破壞了cache的命中率,對性能是有損的。
所以我們需要細心的分析哪些變量需要使用volatile修飾,但是即使開發(fā)人員意識到volatile可以解決內(nèi)存不可見問題,但是從系統(tǒng)中找出哪些變量需要使用volatile或者原子性結(jié)構(gòu)進行修飾也是一個困難的事情,這使得我們在非業(yè)務(wù)邏輯處理上需要耗掉一部分精力。
小結(jié):
對調(diào)用堆棧的誤解
提起調(diào)用堆棧( Call stacks)大家都耳熟能詳,但是其被發(fā)明是在并發(fā)編程不是那么重要時候,那時候多核cpu系統(tǒng)還不常見,所以調(diào)用堆棧不會跨線程,因此不會為異步調(diào)用鏈提供調(diào)用堆棧能力。
在多線程下,當主線程(調(diào)用線程)開啟一個異步線程運行異步任務(wù)時候,問題就出現(xiàn)了。這時候主線程會將共享對象放到異步線程可以訪問到的共享內(nèi)存里面,然后開啟異步線程后主線程繼續(xù)做自己的事情,而異步線程則會從共享內(nèi)存中訪問到主線程創(chuàng)建的共享對象,然后進行異步處理。
進行異步處理時候遇到的第一個問題是當異步線程執(zhí)行完畢任務(wù)后,如何通知主線程?另外當異步任務(wù)執(zhí)行出現(xiàn)異常時候該怎么做?這個異常將會被異步線程捕獲,并且不會傳遞給主調(diào)用線程。
理論上主調(diào)用線程需要在異步任務(wù)執(zhí)行完畢或者出異常時候被通知,但是沒有調(diào)用堆??梢詡鬟f異常。異步任務(wù)執(zhí)行失敗的的通知只能通過輔助方式完成,比如Future方式,將錯誤碼寫到主調(diào)用線程所在的地方,否則一旦準備好就希望得到結(jié)果。如果沒有此通知,則主調(diào)用線程將永遠不會收到失敗通知,并且任務(wù)將丟失!這類似于網(wǎng)絡(luò)系統(tǒng)的工作方式,其中消息/請求可能會丟失/失敗而不會發(fā)出任何通知。
當真的發(fā)生錯誤時,這種情況會變得更糟,當異步工作線程遇到錯誤時候會導(dǎo)致最終陷入無法恢復(fù)的境地。例如由錯誤引起的內(nèi)部異常會冒泡到線程的根,并使線程關(guān)閉。這立即引發(fā)了一個問題,誰應(yīng)該重新啟動該異步線程執(zhí)行的任務(wù),以及如何將其還原到已知狀態(tài)?乍一看,這似乎是可以管理的,但我們突然遇到了一個新的現(xiàn)象:異步線程當前正在執(zhí)行的實際任務(wù)我們并沒有存放起來。實際上,由于到達頂部的異常使所有調(diào)用棧退出,任務(wù)狀態(tài)已經(jīng)完全丟失了!即使這是本地通信,也沒有網(wǎng)絡(luò)連接,但是我們還是丟失了一條消息(可能會丟失消息)。
小結(jié):
為了在當前系統(tǒng)上實現(xiàn)任何有意義的并發(fā)性和提高性能,線程必須以高效的方式在彼此之間委派任務(wù),而不會阻塞。使用這種類型的任務(wù)委派并發(fā)(甚至在網(wǎng)絡(luò)/分布式計算中更是如此),基于調(diào)用堆棧的錯誤處理會導(dǎo)致崩潰。因此需要引入新的顯式錯誤信令機制,讓失敗成為域模型的一部分。
具有工作委派的并發(fā)系統(tǒng)需要處理服務(wù)故障,并需要具有從故障中恢復(fù)的原則方法。此類服務(wù)的客戶端需要注意,任務(wù)/消息可能會在重新啟動期間丟失。即使沒有發(fā)生損失,由于先前排隊的任務(wù)(較長的隊列)或者垃圾回收導(dǎo)致的延遲等,將會導(dǎo)致響應(yīng)可能會被任意延遲。面對這些情況,并發(fā)系統(tǒng)應(yīng)以超時的形式處理響應(yīng)截止日期。
本文名稱:Java傳統(tǒng)編程模型存在的問題
文章起源:http://www.rwnh.cn/news14/103314.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計公司、網(wǎng)站設(shè)計、企業(yè)網(wǎng)站制作、域名注冊、網(wǎng)站內(nèi)鏈、靜態(tài)網(wǎng)站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容