【面试必备】java写spark好不好
并發(fā)編程三大特性
原子性
一個操作或者多次操作,要么所有的操作全部都得到執(zhí)行并且不會受到任何因素的干擾而中斷,要么所有的操作都執(zhí)行,要么都不執(zhí)行。
對于基本數(shù)據(jù)類型的訪問,讀寫都是原子性的【long和double可能例外】。
如果需要更大范圍的原子性保證,可以使用synchronized關(guān)鍵字滿足。
可見性
當(dāng)一個變量對共享變量進(jìn)行了修改,另外的線程都能立即看到修改后的最新值。
volatile保證共享變量可見性,除此之外,synchronized和final都可以 實(shí)現(xiàn)可見性。
synchronized:對一個變量執(zhí)行unclock之前,必須先把此變量同步回主內(nèi)存中。
final:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒有把this的引用傳遞出去,其他線程中就能夠看見final字段的值。
有序性
即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行【由于指令重排序的存在,Java 在編譯器以及運(yùn)行期間對輸入代碼進(jìn)行優(yōu)化,代碼的執(zhí)行順序未必就是編寫代碼時候的順序】,volatile通過禁止指令重排序保證有序性,除此之外,synchronized關(guān)鍵字也可以保證有序性,由【一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作】這條規(guī)則獲得。
CPU緩存模型是什么
高速緩存為何出現(xiàn)?
計(jì)算機(jī)在執(zhí)行程序時,每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過程中,勢必涉及到數(shù)據(jù)的讀取和寫入。由于程序運(yùn)行過程中的**臨時數(shù)據(jù)是存放在主存(物理內(nèi)存)**當(dāng)中的,這時就存在一個問題,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多,因此如果任何時候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進(jìn)行,會大大降低指令執(zhí)行的速度。
為了解決CPU處理速度和內(nèi)存不匹配的問題,CPU Cache出現(xiàn)了。
圖源:JavaGuide
緩存一致性問題
當(dāng)程序在運(yùn)行過程中,會將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計(jì)算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當(dāng)運(yùn)算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。
在單線程中運(yùn)行是沒有任何問題的,但是在多線程環(huán)境下問題就會顯現(xiàn)。舉個簡單的例子,如下面這段代碼:
i = i + 1;按照上面分析,主要分為如下幾步:
- 從主存讀取i的值,復(fù)制一份到高速緩存中。
- CPU執(zhí)行執(zhí)行執(zhí)行對i進(jìn)行加1操作,將數(shù)據(jù)寫入高速緩存。
- 運(yùn)算結(jié)束后,將高速緩存中的數(shù)據(jù)刷新到內(nèi)存中。
多線程環(huán)境下,可能出現(xiàn)什么現(xiàn)象呢?
- 初始時,兩個線程分別讀取i的值,存入各自所在的CPU高速緩存中。
- 線程T1進(jìn)行加1操作,將i的最新值1寫入內(nèi)存。
- 此時線程T2的高速緩存中i的值還是0,進(jìn)行加1操作,并將i的最新值1寫入內(nèi)存。
最終的結(jié)果i = 1而不是i = 2,得出結(jié)論:如果一個變量在多個CPU中都存在緩存(一般在多線程編程時才會出現(xiàn)),那么就可能存在緩存不一致的問題。
如何解決緩存不一致
解決緩存不一致的問題,通常來說有如下兩種解決方案【都是在硬件層面上提供的方式】:
通過在總線加LOCK#鎖的方式
在早期的CPU當(dāng)中,是通過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。因?yàn)镃PU和其他部件進(jìn)行通信都是通過總線來進(jìn)行的,如果對總線加LOCK#鎖的話,也就是說阻塞了其他CPU對其他部件訪問(如內(nèi)存),從而使得只能有一個CPU能使用這個變量的內(nèi)存。比如上面例子中 如果一個線程在執(zhí)行 i = i +1,如果在執(zhí)行這段代碼的過程中,在總線上發(fā)出了LCOK#鎖的信號,那么只有等待這段代碼完全執(zhí)行完畢之后,其他CPU才能從變量i所在的內(nèi)存讀取變量,然后進(jìn)行相應(yīng)的操作。這樣就解決了緩存不一致的問題。
但,有一個問題,在鎖住總線期間,其他CPU無法訪問內(nèi)存,導(dǎo)致效率低下,于是就出現(xiàn)了下面的緩存一致性協(xié)議。
通過緩存一致性協(xié)議
較著名的就是Intel的MESI協(xié)議,MESI協(xié)議保S證了每個緩存中使用的共享變量的副本是一致的。
當(dāng)CPU寫數(shù)據(jù)時,如果發(fā)現(xiàn)操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發(fā)出信號通知其他CPU將該變量的緩存行置為無效狀態(tài),因此當(dāng)其他CPU需要讀取這個變量時,發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的【嗅探機(jī)制:每個處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己的緩存的值是否過期】,那么它就會從內(nèi)存重新讀取。
基于MESI一致性協(xié)議,每個處理器需要不斷從主內(nèi)存嗅探和CAS不斷循環(huán),無效交互會導(dǎo)致總線帶寬達(dá)到峰值,出現(xiàn)總線風(fēng)暴。
JMM內(nèi)存模型是什么
JMM【Java Memory Model】:Java內(nèi)存模型,是java虛擬機(jī)規(guī)范中所定義的一種內(nèi)存模型,Java內(nèi)存模型是標(biāo)準(zhǔn)化的,屏蔽掉了底層不同計(jì)算機(jī)的區(qū)別,以實(shí)現(xiàn)讓Java程序在各種平臺下都能達(dá)到一致的內(nèi)存訪問效果。
它描述了Java程序中各種變量【線程共享變量】的訪問規(guī)則,以及在JVM中將變量存儲到內(nèi)存和從內(nèi)存中讀取變量這樣的底層細(xì)節(jié)。
注意,為了獲得較好的執(zhí)行性能,Java內(nèi)存模型并沒有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來提升指令執(zhí)行速度,也沒有限制編譯器對指令進(jìn)行重排序。也就是說,在java內(nèi)存模型中,也會存在緩存一致性問題和指令重排序的問題。
JMM的規(guī)定
所有的共享變量都存儲于主內(nèi)存,這里所說的變量指的是【實(shí)例變量和類變量】,不包含局部變量,因?yàn)?strong>局部變量是線程私有的,因此不存在競爭問題。
每個線程都有自己的工作內(nèi)存(類似于前面的高速緩存)。線程對變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對主存進(jìn)行操作。
每個線程不能訪問其他線程的工作內(nèi)存。
Java對三大特性的保證
原子性
在Java中,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作,即這些操作是不可被中斷的,要么執(zhí)行,要么不執(zhí)行。
為了更好地理解上面這句話,可以看看下面這四個例子:
x = 10; //1 y = x; //2 x ++; //3 x = x + 1; //4需要注意的點(diǎn):
- 在32位平臺下,對64位數(shù)據(jù)的讀取和賦值是需要通過兩個操作來完成的,不能保證其原子性。在目前64位JVM中,已經(jīng)保證對64位數(shù)據(jù)的讀取和賦值也是原子性操作了。
- Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作,如果要實(shí)現(xiàn)更大范圍操作的原子性,可以通過synchronized和Lock來實(shí)現(xiàn)。
可見性
Java提供了volatile關(guān)鍵字來保證可見性。
當(dāng)一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當(dāng)有其他線程需要讀取時,它會去內(nèi)存中讀取新值。
另外,通過synchronized和Lock也能夠保證可見性,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當(dāng)中。因此可以保證可見性。
有序性
在Java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序,但是重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性。
在Java里面,可以通過volatile關(guān)鍵字來保證有序性,另外也可以通過synchronized和Lock來保證有序性。
Java內(nèi)存模型具備一些先天的有序性,前提是兩個操作滿足happens-before原則,摘自《深入理解Java虛擬機(jī)》:
- 程序次序規(guī)則:一個線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作【讓程序看起來像是按照代碼順序執(zhí)行,虛擬機(jī)只會對不存在數(shù)據(jù)依賴性的指令進(jìn)行重排序,只能保證單線程中執(zhí)行結(jié)果的正確性,多線程結(jié)果正確性卻無法保證】
- 鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖額lock操作
- volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作
- 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C
- 線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個一個動作
- 線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生
- 線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結(jié)束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
- 對象終結(jié)規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始
如果兩個操作的執(zhí)行次序無法從happens-before原則推導(dǎo)出來,那么它們就不能保證它們的有序性,虛擬機(jī)可以隨意地對它們進(jìn)行重排序。
volatile解決的問題
-
保證了不同線程對共享變量【類的成員變量,類的靜態(tài)成員變量】進(jìn)行操作是時的可見性,一個線程修改了某個變量的值,新值對其他線程來說是立即可見的。
-
禁止指令重排序。
舉個簡單的例子,看下面這段代碼:
//線程1 boolean volatile stop = false; while(!stop){doSomething(); } //線程2 stop = true;volatile保證原子性嗎?怎么解決?
volatile無法保證原子性,如對一個volatile修飾的變量進(jìn)行自增操作i ++,無法保證多線程下結(jié)果的正確性。
解決方法:
- 使用synchronized關(guān)鍵字或者Lock加鎖,保證某個代碼塊 在同一時刻只能被一個線程執(zhí)行。
- 使用JUC包下的原子類,如AtomicInteger等。【Atomic利用CAS來實(shí)現(xiàn)原子操作】。
volatile的實(shí)現(xiàn)原理
下面這段話摘自《深入理解Java虛擬機(jī)》:
觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時,會多出一個lock前綴指令。
lock前綴指令實(shí)際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄),內(nèi)存屏障會提供3個功能:
- 它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成;
- 它會強(qiáng)制將對緩存的修改操作立即寫入主存;
- 如果是寫操作,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效。
volatile和synchronized的區(qū)別
volatile變量讀操作的性能消耗與普通變量幾乎沒有什么差別,但是寫操作則會慢一些,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。不過即便如此,大多數(shù)場景下volatile的總開銷仍然要比鎖來的低。
- volatile只能用于變量,而synchronized可以修飾方法以及代碼塊。
- volatile能保證可見性,但是不能保證原子性。synchronized兩者都能保證。如果只是對一個共享變量進(jìn)行多個線程的賦值,而沒有其他的操作,推薦使用volatile,它更加輕量級。
- volatile 關(guān)鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized 關(guān)鍵字解決的是多個線程之間訪問資源的同步性。
volatile的使用條件
使用volatile必須具備兩個條件【保證原子】:
- 對變量的寫操作不依賴于當(dāng)前值。
- 該變量沒有包含在具有其他變量的不變式中。
Java面試核心知識點(diǎn)筆記
其中囊括了JVM、鎖、并發(fā)、Java反射、Spring原理、微服務(wù)、Zookeeper、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等大量知識點(diǎn)。
Java中高級面試高頻考點(diǎn)整理
更多Java進(jìn)階知識筆記文檔分享,這些對于面試還是學(xué)習(xí)來說都是一份不錯的學(xué)習(xí)資料
有需要的朋友可以戳這里即可免費(fèi)領(lǐng)取
最后還分享Java進(jìn)階學(xué)習(xí)及面試必備的視頻教學(xué)
更多Java進(jìn)階知識筆記文檔分享,這些對于面試還是學(xué)習(xí)來說都是一份不錯的學(xué)習(xí)資料
有需要的朋友可以戳這里即可免費(fèi)領(lǐng)取
[外鏈圖片轉(zhuǎn)存中…(img-ujMXkyia-1626689158699)]
最后還分享Java進(jìn)階學(xué)習(xí)及面試必備的視頻教學(xué)
[外鏈圖片轉(zhuǎn)存中…(img-EN32CykJ-1626689158701)]
總結(jié)
以上是生活随笔為你收集整理的【面试必备】java写spark好不好的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【面试必会】java虚拟机原理
- 下一篇: 成都欢乐谷赛车要花钱吗