日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【面试必备】java写spark好不好

發(fā)布時間:2023/11/30 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面试必备】java写spark好不好 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

并發(fā)編程三大特性

原子性

一個操作或者多次操作,要么所有的操作全部都得到執(zhí)行并且不會受到任何因素的干擾而中斷,要么所有的操作都執(zhí)行,要么都不執(zhí)行

對于基本數(shù)據(jù)類型的訪問,讀寫都是原子性的【long和double可能例外】。

如果需要更大范圍的原子性保證,可以使用synchronized關(guān)鍵字滿足。

可見性

當(dāng)一個變量對共享變量進(jìn)行了修改,另外的線程都能立即看到修改后的最新值。

volatile保證共享變量可見性,除此之外,synchronized和final都可以 實現(xiàn)可見性。

synchronized:對一個變量執(zhí)行unclock之前,必須先把此變量同步回主內(nèi)存中。

final:被final修飾的字段在構(gòu)造器中一旦被初始化完成,并且構(gòu)造器沒有把this的引用傳遞出去,其他線程中就能夠看見final字段的值。

有序性

即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行【由于指令重排序的存在,Java 在編譯器以及運行期間對輸入代碼進(jìn)行優(yōu)化,代碼的執(zhí)行順序未必就是編寫代碼時候的順序】,volatile通過禁止指令重排序保證有序性,除此之外,synchronized關(guān)鍵字也可以保證有序性,由【一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作】這條規(guī)則獲得。

CPU緩存模型是什么

高速緩存為何出現(xiàn)?

計算機在執(zhí)行程序時,每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過程中,勢必涉及到數(shù)據(jù)的讀取和寫入。由于程序運行過程中的**臨時數(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)程序在運行過程中,會將運算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當(dāng)運算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。

在單線程中運行是沒有任何問題的,但是在多線程環(huán)境下問題就會顯現(xiàn)。舉個簡單的例子,如下面這段代碼:

i = i + 1;

按照上面分析,主要分為如下幾步:

  • 從主存讀取i的值,復(fù)制一份到高速緩存中。
  • CPU執(zhí)行執(zhí)行執(zhí)行對i進(jìn)行加1操作,將數(shù)據(jù)寫入高速緩存。
  • 運算結(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#鎖的形式來解決緩存不一致的問題。因為CPU和其他部件進(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)自己緩存中緩存該變量的緩存行是無效的【嗅探機制:每個處理器通過嗅探在總線上傳播的數(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虛擬機規(guī)范中所定義的一種內(nèi)存模型,Java內(nèi)存模型是標(biāo)準(zhǔn)化的,屏蔽掉了底層不同計算機的區(qū)別,以實現(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)存,這里所說的變量指的是【實例變量和類變量】,不包含局部變量,因為局部變量是線程私有的,因此不存在競爭問題。

每個線程都有自己的工作內(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
  • 只有語句1是原子性操作:直接將數(shù)值10賦值給x,也就是說線程執(zhí)行這個語句的會直接將數(shù)值10寫入到工作內(nèi)存中
  • 語句2實際包含兩個操作:先去讀取x的值,再將x的值寫入工作內(nèi)存,雖然兩步分別都是原子操作,但是合起來就不能算作原子操作了。
  • 語句3和4表示:先讀取x的值,進(jìn)行加1操作,寫入新的值。
  • 需要注意的點:

    • 在32位平臺下,對64位數(shù)據(jù)的讀取和賦值是需要通過兩個操作來完成的,不能保證其原子性。在目前64位JVM中,已經(jīng)保證對64位數(shù)據(jù)的讀取和賦值也是原子性操作了。
    • Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作,如果要實現(xiàn)更大范圍操作的原子性,可以通過synchronized和Lock來實現(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虛擬機》:

    • 程序次序規(guī)則:一個線程內(nèi),按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作【讓程序看起來像是按照代碼順序執(zhí)行,虛擬機只會對不存在數(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ìn)行重排序。

    volatile解決的問題

    • 保證了不同線程對共享變量【類的成員變量,類的靜態(tài)成員變量】進(jìn)行操作是時的可見性,一個線程修改了某個變量的值,新值對其他線程來說是立即可見的

    • 禁止指令重排序。

    舉個簡單的例子,看下面這段代碼:

    //線程1 boolean volatile stop = false; while(!stop){doSomething(); } //線程2 stop = true;
  • 線程1和2各自都擁有自己的工作內(nèi)存,線程1和線程2首先都會將stop變量的值拷貝一份放到自己的工作內(nèi)存中,
  • 共享變量stop通過volatile修飾,線程2將stop的值改為true將會立即寫入主內(nèi)存。
  • 線程2寫入主內(nèi)存之后,導(dǎo)致線程1工作內(nèi)存中緩存變量stop的緩存行無效。
  • 線程1的工作內(nèi)存中緩存變量stop的緩存行無效,導(dǎo)致線程1會再次從主存中讀取stop值。
  • volatile保證原子性嗎?怎么解決?

    volatile無法保證原子性,如對一個volatile修飾的變量進(jìn)行自增操作i ++,無法保證多線程下結(jié)果的正確性。

    解決方法:

    • 使用synchronized關(guān)鍵字或者Lock加鎖,保證某個代碼塊 在同一時刻只能被一個線程執(zhí)行。
    • 使用JUC包下的原子類,如AtomicInteger等?!続tomic利用CAS來實現(xiàn)原子操作】。

    volatile的實現(xiàn)原理

    下面這段話摘自《深入理解Java虛擬機》:

    觀察加入volatile關(guān)鍵字和沒有加入volatile關(guān)鍵字時所生成的匯編代碼發(fā)現(xiàn),加入volatile關(guān)鍵字時,會多出一個lock前綴指令。

    lock前綴指令實際上相當(dāng)于一個內(nèi)存屏障(也成內(nèi)存柵欄),內(nèi)存屏障會提供3個功能:

    • 它確保指令重排序時不會把其后面的指令排到內(nèi)存屏障之前的位置,也不會把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時,在它前面的操作已經(jīng)全部完成;
    • 它會強制將對緩存的修改操作立即寫入主存;
    • 如果是寫操作,它會導(dǎo)致其他CPU中對應(yīng)的緩存行無效。

    volatile和synchronized的區(qū)別

    volatile變量讀操作的性能消耗與普通變量幾乎沒有什么差別,但是寫操作則會慢一些,因為它需要在本地代碼中插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行。不過即便如此,大多數(shù)場景下volatile的總開銷仍然要比鎖來的低。

    • volatile只能用于變量,而synchronized可以修飾方法以及代碼塊。
    • volatile能保證可見性,但是不能保證原子性。synchronized兩者都能保證。如果只是對一個共享變量進(jìn)行多個線程的賦值,而沒有其他的操作,推薦使用volatile,它更加輕量級。
    • volatile 關(guān)鍵字主要用于解決變量在多個線程之間的可見性,而 synchronized 關(guān)鍵字解決的是多個線程之間訪問資源的同步性。

    volatile的使用條件

    使用volatile必須具備兩個條件【保證原子】:

    • 對變量的寫操作不依賴于當(dāng)前值。
    • 該變量沒有包含在具有其他變量的不變式中。

    Java面試核心知識點筆記

    其中囊括了JVM、鎖、并發(fā)、Java反射、Spring原理、微服務(wù)、Zookeeper、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等大量知識點。

    Java中高級面試高頻考點整理

    更多Java進(jìn)階知識筆記文檔分享,這些對于面試還是學(xué)習(xí)來說都是一份不錯的學(xué)習(xí)資料

    有需要的朋友可以戳這里即可免費領(lǐng)取

    最后還分享Java進(jìn)階學(xué)習(xí)及面試必備的視頻教學(xué)

    更多Java進(jìn)階知識筆記文檔分享,這些對于面試還是學(xué)習(xí)來說都是一份不錯的學(xué)習(xí)資料

    有需要的朋友可以戳這里即可免費領(lǐng)取

    [外鏈圖片轉(zhuǎn)存中…(img-ujMXkyia-1626689158699)]

    最后還分享Java進(jìn)階學(xué)習(xí)及面試必備的視頻教學(xué)

    [外鏈圖片轉(zhuǎn)存中…(img-EN32CykJ-1626689158701)]

    總結(jié)

    以上是生活随笔為你收集整理的【面试必备】java写spark好不好的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。