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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

吃透这JAVA并发十二核心,面试官都得对你刮目相看

發(fā)布時(shí)間:2023/12/20 编程问答 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 吃透这JAVA并发十二核心,面试官都得对你刮目相看 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1、HashMap

面試第一題必問的 HashMap,挺考驗(yàn)Javaer的基礎(chǔ)功底的,別問為啥放在這,因?yàn)橹匾?#xff01;HashMap具有如下特性

  • HashMap 的存取是沒有順序的。
  • KV 均允許為 NULL。
  • 多線程情況下該類不安全,可以考慮用 HashTable。
  • JDk8底層是數(shù)組 + 鏈表 + 紅黑樹,JDK7底層是數(shù)組 + 鏈表。
  • 初始容量和裝載因子是決定整個(gè)類性能的關(guān)鍵點(diǎn),輕易不要?jiǎng)印?/li>
  • HashMap是懶漢式創(chuàng)建的,只有在你put數(shù)據(jù)時(shí)候才會 build。
  • 單向鏈表轉(zhuǎn)換為紅黑樹的時(shí)候會先變化為雙向鏈表最終轉(zhuǎn)換為紅黑樹,切記雙向鏈表跟紅黑樹是共存的。
  • 對于傳入的兩個(gè)key,會強(qiáng)制性的判別出個(gè)高低,目的是為了決定向左還是向右放置數(shù)據(jù)。
  • 鏈表轉(zhuǎn)紅黑樹后會努力將紅黑樹的root節(jié)點(diǎn)和鏈表的頭節(jié)點(diǎn) 跟table[i]節(jié)點(diǎn)融合成一個(gè)。
  • 在刪除的時(shí)候是先判斷刪除節(jié)點(diǎn)紅黑樹個(gè)數(shù)是否需要轉(zhuǎn)鏈表,不轉(zhuǎn)鏈表就跟RBT類似,找個(gè)合適的節(jié)點(diǎn)來填充已刪除的節(jié)點(diǎn)。
  • 紅黑樹的root節(jié)點(diǎn)不一定跟table[i]也就是鏈表的頭節(jié)點(diǎn)是同一個(gè),三者同步是靠MoveRootToFront實(shí)現(xiàn)的。而HashIterator.remove()會在調(diào)用removeNode的時(shí)候movable=false。
  • 常見HashMap考點(diǎn):

  • HashMap原理,內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
  • HashMap中的put、get、remove大致過程。
  • HashMap中 hash函數(shù)實(shí)現(xiàn)。
  • HashMap如何擴(kuò)容。
  • HashMap幾個(gè)重要參數(shù)為什么這樣設(shè)定。
  • HashMap為什么線程不安全,如何替換。
  • HashMap在JDK7跟JDK8中的區(qū)別。
  • HashMap中鏈表跟紅黑樹切換思路。
  • JDK7中 HashMap環(huán)產(chǎn)生原理。
  • 2、ConcurrentHashMap

    ConcurrentHashMap是多線程模式下常用的并發(fā)容器,它的實(shí)現(xiàn)在JDK7JDK8區(qū)別挺大的。

    2.1 JDK7

    JDK7中的 ConcurrentHashMap 使用?Segment?+?HashEntry?分段鎖實(shí)現(xiàn)并發(fā),它的缺點(diǎn)是并發(fā)程度是由Segment?數(shù)組個(gè)數(shù)來決定的,并發(fā)度一旦初始化無法擴(kuò)容,擴(kuò)容的話只是HashEntry的擴(kuò)容。

    Segment?繼承自?ReentrantLock,在此扮演鎖的角色。可以理解為我們的每個(gè)Segment都是實(shí)現(xiàn)了Lock功能的HashMap。如果我們同時(shí)有多個(gè)Segment形成了Segment數(shù)組那我們就可以實(shí)現(xiàn)并發(fā)咯。

    大致的put流程如下:

    • ConcurrentHashMap底層大致實(shí)現(xiàn)?

    ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)。它使用了多個(gè)鎖來控制對hash表的不同部分進(jìn)行的修改。內(nèi)部使用段(Segment)來表示這些不同的部分,每個(gè)段其實(shí)就是一個(gè)小的HashTable,只要多個(gè)修改操作發(fā)生在不同的段上就可以并發(fā)進(jìn)行。

    • ConcurrentHashMap在并發(fā)下的情況下如何保證取得的元素是最新的?

    用于存儲鍵值對數(shù)據(jù)的HashEntry,在設(shè)計(jì)上它的成員變量valuenext都是volatile類型的,這樣就保證別的線程對value值的修改,get方法可以馬上看到,并且get的時(shí)候是不用加鎖的

    • ConcurrentHashMap弱一致性體現(xiàn)在clear和get方法,原因在于沒有加鎖

    比如迭代器在遍歷數(shù)據(jù)的時(shí)候是一個(gè)Segment一個(gè)Segment去遍歷的,如果在遍歷完一個(gè)Segment時(shí)正好有一個(gè)線程在剛遍歷完的Segment上插入數(shù)據(jù),就會體現(xiàn)出不一致性。clear也是一樣。get方法和containsKey方法都是遍歷對應(yīng)索引位上所有節(jié)點(diǎn),都是不加鎖來判斷的,如果是修改性質(zhì)的因?yàn)榭梢娦缘拇嬖诳梢灾苯荧@得最新值,不過如果是新添加值則無法保持一致性

    • size?統(tǒng)計(jì)個(gè)數(shù)不準(zhǔn)確

    size方法比較有趣,先無鎖的統(tǒng)計(jì)所有的數(shù)據(jù)量看下前后兩次是否數(shù)據(jù)一樣,如果一樣則返回?cái)?shù)據(jù),如果不一樣則要把全部的segment進(jìn)行加鎖,統(tǒng)計(jì),解鎖。并且size方法只是返回一個(gè)統(tǒng)計(jì)性的數(shù)字。

    2.2 JDK8

    ConcurrentHashMap 在JDK8中拋棄了分段鎖,轉(zhuǎn)為用?CAS?+?synchronized,同時(shí)將HashEntry改為Node,還加入了紅黑樹的實(shí)現(xiàn),主要還是看put的流程(如果看了擴(kuò)容這塊,絕對可以好好吹逼一番)。

    ConcurrentHashMap?是如果來做到高效并發(fā)安全?

    • 讀操作

    get方法中根本沒有使用同步機(jī)制,也沒有使用unsafe方法,所以讀操作是支持并發(fā)操作的。

    • 寫操作

    基本思路跟HashMap的寫操作類似,只不過用到了CAS?+?syn?實(shí)現(xiàn)加鎖,同時(shí)還涉及到擴(kuò)容的操作。JDK8中鎖已經(jīng)細(xì)化到?table[i]?了,數(shù)組位置不同可并發(fā),位置相同則去幫忙擴(kuò)容。

    • 同步處理主要是通過synunsafe的硬件級別原子性這兩種方式完成

    當(dāng)我們對某個(gè)table[i]操作時(shí)候是用syn加鎖的。取數(shù)據(jù)的時(shí)候用的是unsafe硬件級別指令,直接獲取指定內(nèi)存的最新數(shù)據(jù)。

    3 、并發(fā)基礎(chǔ)知識

    并發(fā)編程的出發(fā)點(diǎn):充分利用CPU計(jì)算資源,多線程并不是一定比單線程快,要不為什么 Redis 6.0版本的核心操作指令仍然是單線程呢?對吧!

    多線程跟單線程的性能都要具體任務(wù)具體分析,talk is cheap, show me the picture

    3.1 進(jìn)程跟線程

    進(jìn)程

    進(jìn)程是操作系統(tǒng)調(diào)用的最小單位,是系統(tǒng)進(jìn)行資源分配和調(diào)度的獨(dú)立單位。

    線程

  • 因?yàn)檫M(jìn)程的創(chuàng)建、銷毀、切換產(chǎn)生大量的時(shí)間和空間的開銷,進(jìn)程的數(shù)量不能太多,而線程是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,他是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度的最小單位。線程可以減少程序并發(fā)執(zhí)行時(shí)的時(shí)間和空間開銷,使得操作系統(tǒng)具有更好的并發(fā)性。
  • 線程基本不擁有系統(tǒng)資源,只有一些運(yùn)行時(shí)必不可少的資源,比如程序計(jì)數(shù)器、寄存器和棧,進(jìn)程則占有堆、棧。線程,Java默認(rèn)有兩個(gè)線程 main 跟GC。Java是沒有權(quán)限開線程的,無法操作硬件,都是調(diào)用的?native?的?start0?方法 由?C++?實(shí)現(xiàn)
  • 3.2 并行跟并發(fā)

    并發(fā):

    concurrency?: 多線程操作同一個(gè)資源,單核CPU極速的切換運(yùn)行多個(gè)任務(wù)

    并行:

    parallelism?:多個(gè)CPU同時(shí)使用,CPU多核 真正的同時(shí)執(zhí)行

    3.3 線程幾個(gè)狀態(tài)

    Java中線程的狀態(tài)分為6種:

    • 初始(New):

    新創(chuàng)建了一個(gè)線程對象,但還沒有調(diào)用start()方法。

    • 可運(yùn)行(Runnable):
  • 調(diào)用線程的start()方法,此線程進(jìn)入就緒狀態(tài)。就緒狀態(tài)只是說你資格運(yùn)行,調(diào)度程序沒有給你CPU資源,你就永遠(yuǎn)是就緒狀態(tài)。
  • 當(dāng)前線程sleep()方法結(jié)束,其他線程join()結(jié)束,等待用戶輸入完畢,某個(gè)線程拿到對象鎖,這些線程也將進(jìn)入就緒狀態(tài)。
  • 當(dāng)前線程時(shí)間片用完了,調(diào)用當(dāng)前線程的yield()方法,當(dāng)前線程進(jìn)入就緒狀態(tài)。
  • 鎖池里的線程拿到對象鎖后,進(jìn)入就緒狀態(tài)
    • 運(yùn)行中(Running)

    就緒狀態(tài)的線程在獲得CPU時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài)(running)。這也是線程進(jìn)入運(yùn)行狀態(tài)的唯一的一種方式。

    • 阻塞(Blocked):

    阻塞狀態(tài)是線程阻塞在進(jìn)入synchronized關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài)。

    • 等待(Waiting) 跟 超時(shí)等待(Timed_Waiting):
  • 處于這種狀態(tài)的線程不會被分配CPU執(zhí)行時(shí)間,它們要等待被顯式地喚醒(通知或中斷),否則會處于無限期等待的狀態(tài)。
  • 處于這種狀態(tài)的線程不會被分配CPU執(zhí)行時(shí)間,不過無須無限期等待被其他線程顯示地喚醒,在達(dá)到一定時(shí)間后它們會自動喚醒。
    • 終止(Terminated):

    當(dāng)線程正常運(yùn)行結(jié)束或者被異常中斷后就會被終止。線程一旦終止了,就不能復(fù)生。

    PS:

  • 調(diào)用?obj.wait?的線程需要先獲取?obj?的?monitorwait會釋放?obj?的?monitor?并進(jìn)入等待態(tài)。所以?wait()/notify()?都要與?synchronized?聯(lián)用。
  • 其實(shí)線程從阻塞/等待狀態(tài) 到 可運(yùn)行狀態(tài)都涉及到同步隊(duì)列等待隊(duì)列的,這點(diǎn)在?AQS?有講。
  • 3.4. 阻塞與等待的區(qū)別

    阻塞

    當(dāng)一個(gè)線程試圖獲取對象鎖(非JUC庫中的鎖,即synchronized),而該鎖被其他線程持有,則該線程進(jìn)入阻塞狀態(tài)。它的特點(diǎn)是使用簡單,由JVM調(diào)度器來決定喚醒自己,而不需要由另一個(gè)線程來顯式喚醒自己,不響應(yīng)中斷。

    等待

    當(dāng)一個(gè)線程等待另一個(gè)線程通知調(diào)度器一個(gè)條件時(shí),該線程進(jìn)入等待狀態(tài)。它的特點(diǎn)是需要等待另一個(gè)線程顯式地喚醒自己,實(shí)現(xiàn)靈活,語義更豐富,可響應(yīng)中斷。例如調(diào)用:Object.wait()Thread.join()以及等待?Lock?或?Condition

    雖然?synchronized 和 JUC 里的 Lock 都實(shí)現(xiàn)鎖的功能,但線程進(jìn)入的狀態(tài)是不一樣的。synchronized 會讓線程進(jìn)入阻塞態(tài),而 JUC 里的 Lock是用park()/unpark() 來實(shí)現(xiàn)阻塞/喚醒 的,會讓線程進(jìn)入等待狀態(tài)。雖然等鎖時(shí)進(jìn)入的狀態(tài)不一樣,但被喚醒后又都進(jìn)入Runnable狀態(tài),從行為效果來看又是一樣的。

    3.5 yield 跟 sleep 區(qū)別

  • yield?跟?sleep?都能暫停當(dāng)前線程,都不會釋放鎖資源sleep?可以指定具體休眠的時(shí)間,而?yield?則依賴?CPU?的時(shí)間片劃分。
  • sleep方法給其他線程運(yùn)行機(jī)會時(shí)不考慮線程的優(yōu)先級,因此會給低優(yōu)先級的線程以運(yùn)行的機(jī)會。yield方法只會給相同優(yōu)先級或更高優(yōu)先級的線程以運(yùn)行的機(jī)會。
  • 調(diào)用?sleep?方法使線程進(jìn)入等待狀態(tài),等待休眠時(shí)間達(dá)到,而調(diào)用我們的yield方法,線程會進(jìn)入就緒狀態(tài),也就是sleep需要等待設(shè)置的時(shí)間后才會進(jìn)行就緒狀態(tài),而yield會立即進(jìn)入就緒狀態(tài)
  • sleep方法聲明會拋出?InterruptedException,而?yield?方法沒有聲明任何異常
  • yield?不能被中斷,而?sleep?則可以接受中斷。
  • sleep方法比yield方法具有更好的移植性(跟操作系統(tǒng)CPU調(diào)度相關(guān))
  • 3.6 wait 跟 sleep 區(qū)別

  • 來源不同
  • wait?來自Objectsleep?來自?Thread

  • 是否釋放鎖
  • wait?釋放鎖,sleep?不釋放

  • 使用范圍
  • wait?必須在同步代碼塊中,sleep?可以任意使用

  • 捕捉異常
  • wait?不需要捕獲異常,sleep?需捕獲異常

    3.7 多線程實(shí)現(xiàn)方式

  • 繼承?Thread,實(shí)現(xiàn)run方法
  • 實(shí)現(xiàn)?Runnable接口中的run方法,然后用Thread包裝下。Thread?是線程對象,Runnable?是任務(wù),線程啟動的時(shí)候一定是對象。
  • 實(shí)現(xiàn)?Callable接口,FutureTask?包裝實(shí)現(xiàn)接口,Thread?包裝FutureTaskCallable?與?Runnable?的區(qū)別在于Callablecall方法有返回值,可以拋出異常,Callable有緩存。
  • 通過線程池調(diào)用實(shí)現(xiàn)。
  • 通過Spring的注解?@Async?實(shí)現(xiàn)。
  • 3.8 死鎖

    死鎖是指兩個(gè)或兩個(gè)以上的線程互相持有對方所需要的資源,由于某些鎖的特性,比如syn使用下,一個(gè)線程持有一個(gè)資源,或者說獲得一個(gè)鎖,在該線程釋放這個(gè)鎖之前,其它線程是獲取不到這個(gè)鎖的,而且會一直死等下去,因此這便造成了死鎖。

    面試官:你給我解釋下死鎖是什么,解釋好了我就錄用你。

    應(yīng)聘者:先發(fā)Offer,發(fā)了Offer我給你解釋什么是死鎖。

    產(chǎn)生條件

    互斥條件:一個(gè)資源,或者說一個(gè)鎖只能被一個(gè)線程所占用,當(dāng)一個(gè)線程首先獲取到這個(gè)鎖之后,在該線程釋放這個(gè)鎖之前,其它線程均是無法獲取到這個(gè)鎖的。

    占有且等待:一個(gè)線程已經(jīng)獲取到一個(gè)鎖,再獲取另一個(gè)鎖的過程中,即使獲取不到也不會釋放已經(jīng)獲得的鎖。

    不可剝奪條件:任何一個(gè)線程都無法強(qiáng)制獲取別的線程已經(jīng)占有的鎖

    循環(huán)等待條件:線程A拿著線程B的鎖,線程B拿著線程A的鎖。。

    檢查

    1、jps -l 定位進(jìn)程號

    2、jstack 進(jìn)程號找到死鎖問題

    避免

    加鎖順序:線程按照相同的順序加鎖。

    限時(shí)加鎖:線程獲取鎖的過程中限制一定的時(shí)間,如果給定時(shí)間內(nèi)獲取不到,就算了,這需要用到Lock的一些API。

    4、JMM

    4.1 JMM由來

    隨著CPU內(nèi)存磁盤的高速發(fā)展,它們的訪問速度差別很大。為了提速就引入了L1、L2、L3三級緩存。以后程序運(yùn)行獲取數(shù)據(jù)就是如下的步驟了。

    這樣雖然提速了但是會導(dǎo)致緩存一致性問題跟內(nèi)存可見性問題。同時(shí)編譯器跟CPU為了加速也引入了指令重排。指令重排的大致意思就是你寫的代碼運(yùn)行運(yùn)算結(jié)果會按照你看到的邏輯思維去運(yùn)行,但是在JVM內(nèi)部系統(tǒng)是智能化的會進(jìn)行加速排序的。

    1、編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

    2、指令級并行的重排序:現(xiàn)代處理器采用了指令級并行技術(shù)在不影響數(shù)據(jù)依賴性前提下重排。

    3、內(nèi)存系統(tǒng)的重排序:處理器使用緩存和讀/寫緩沖區(qū) 進(jìn)程重排。

    指令重排這種機(jī)制會導(dǎo)致有序性問題,而在并發(fā)編程時(shí)經(jīng)常會涉及到線程之間的通信跟同步問題,一般說是可見性、原子性、有序性。這三個(gè)問題對應(yīng)的底層就是?緩存一致性內(nèi)存可見性有序性

    原子性:原子性就是指該操作是不可再分的。不論是多核還是單核,具有原子性的量,同一時(shí)刻只能有一個(gè)線程來對它進(jìn)行操作。在整個(gè)操作過程中不會被線程調(diào)度器中斷的操作,都可認(rèn)為是原子性。比如 a = 1。

    可見性:指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其他線程能夠立即看得到修改的值。Java保證可見性可以認(rèn)為通過volatilesynchronizedfinal來實(shí)現(xiàn)。

    有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,Java通過volatilesynchronized來保證。

    為了保證共享內(nèi)存的正確性(可見性、有序性、原子性),內(nèi)存模型定義了共享內(nèi)存模式下多線程程序讀寫操作行為的規(guī)范,既JMM模型,注意JMM只是一個(gè)約定概念,是用來保證效果一致的機(jī)制規(guī)范。它作用于工作內(nèi)存和主存之間數(shù)據(jù)同步過程,規(guī)定了如何做數(shù)據(jù)同步以及什么時(shí)候做數(shù)據(jù)同步。

    在JMM中,有兩條規(guī)定:

    線程對共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行,不能直接從主內(nèi)存中讀寫。不同線程之間無法訪問其他線程工作內(nèi)存中的變量,線程間變量值的傳遞需要通過主內(nèi)存來完成。

    共享變量要實(shí)現(xiàn)可見性,必須經(jīng)過如下兩個(gè)步驟:

    把本地內(nèi)存1中更新過的共享變量刷新到主內(nèi)存中。把主內(nèi)存中最新的共享變量的值更新到本地內(nèi)存2中。

    同時(shí)人們提出了內(nèi)存屏障、happen-before、af-if-serial這三種概念來保證系統(tǒng)的可見性、原子性、有序性。

    4.2 內(nèi)存屏障

    內(nèi)存屏障是一種CPU指令,用于控制特定條件下的重排序內(nèi)存可見性問題。Java編譯器也會根據(jù)內(nèi)存屏障的規(guī)則禁止重排序。Java編譯器在生成指令序列的適當(dāng)位置會插入內(nèi)存屏障指令來禁止特定類型的處理器重排序,從而讓程序按我們預(yù)想的流程去執(zhí)行。具有如下功能:

    保證特定操作的執(zhí)行順序。

    影響某些數(shù)據(jù)(或則是某條指令的執(zhí)行結(jié)果)的內(nèi)存可見性。

    在 volatile 中就用到了內(nèi)存屏障,volatile 部分已詳細(xì)講述。

    4.3 happen-before

    因?yàn)橛兄噶钪嘏诺拇嬖跁?dǎo)致難以理解CPU內(nèi)部運(yùn)行規(guī)則,JDK用 happens-before 的概念來闡述操作之間的內(nèi)存可見性。在 JMM 中如果一個(gè)操作執(zhí)行的結(jié)果需要對另一個(gè)操作可見,那么這兩個(gè)操作之間必須要存在happens-before關(guān)系 。其中CPU的happens-before無需任何同步手段就可以保證的。

    • 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作。
    • 監(jiān)視器鎖規(guī)則:對一個(gè)鎖的解鎖,happens-before于隨后對這個(gè)鎖的加鎖。
    • volatile變量規(guī)則:對一個(gè)volatile域的寫,happens-before于任意后續(xù)對這個(gè)volatile域的讀。
    • 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。
    • start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
    • join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
    • 線程中斷規(guī)則:對線程interrupt方法的調(diào)用happens-before于被中斷線程的代碼檢測到中斷事件的發(fā)生。

    4.4 af-if-serial

    af-if-serial?的含義是不管怎么重排序(編譯器和處理器為了提高并行度),單線程環(huán)境下程序的執(zhí)行結(jié)果不能被改變且必須正確。該語義使單線程環(huán)境下程序員無需擔(dān)心重排序會干擾他們,也無需擔(dān)心內(nèi)存可見性問題。

    5、volatile

    volatile 關(guān)鍵字的引入可以保證變量的可見性,但是無法保證變量的原子性,比如a++這樣的是無法保證的。這里其實(shí)涉及到 JMM 的知識點(diǎn),Java多線程交互是通過共享內(nèi)存的方式實(shí)現(xiàn)的。當(dāng)我們讀寫volatile變量時(shí)具有如下規(guī)則:

    當(dāng)寫一個(gè)volatile變量時(shí),JMM會把該線程對應(yīng)的本地中的共享變量值刷新到主內(nèi)存。

    當(dāng)讀一個(gè)volatile變量時(shí),JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。

    volatile就會用到上面說到的內(nèi)存屏障,目前有四種內(nèi)存屏障:

  • StoreStore屏障,保證普通寫不和volatile寫發(fā)生重排序
  • StoreLoad屏障,保證volatile寫與后面可能的volatile讀寫不發(fā)生重排序
  • LoadLoad屏障,禁止volatile讀與后面的普通讀重排序
  • LoadStore屏障,禁止volatile讀和后面的普通寫重排序
  • volatile 原理:用volatile變量修飾的共享變量進(jìn)行寫操作的時(shí)候會使用CPU提供的Lock前綴指令,在CPU級別的功能如下:

  • 將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到 系統(tǒng)內(nèi)存。
  • 這個(gè)寫回內(nèi)存的操作會告知在其他CPU你們拿到的變量是無效的下一次使用時(shí)候要重新共享內(nèi)存拿。
  • 6、單例模式 DCL + volatile

    6.1 標(biāo)準(zhǔn)單例模式

    高頻考點(diǎn)單例模式:就是將類的構(gòu)造函數(shù)進(jìn)行private化,然后只留出一個(gè)靜態(tài)的Instance?函數(shù)供外部調(diào)用者調(diào)用。單例模式一般標(biāo)準(zhǔn)寫法是?DCL?+?volatile

    public class SingleDcl {private volatile static SingleDcl singleDcl; //保證可見性private SingleDcl(){}public static SingleDcl getInstance(){// 放置進(jìn)入加鎖代碼,先判斷下是否已經(jīng)初始化好了if(singleDcl == null) { // 類鎖 可能會出現(xiàn) AB線程都在這卡著,A獲得鎖,B等待獲得鎖。synchronized (SingleDcl.class) { if(singleDcl == null) {// 如果A線程初始化好了,然后通過vloatile 將變量復(fù)雜給住線程。// 如果此時(shí)沒有singleDel === null,判斷 B進(jìn)程 進(jìn)來后還會再次執(zhí)行 new 語句singleDcl = new SingleDcl();}}}return singleDcl;} }

    6.2 為什么用Volatile修飾

    不用Volatile則代碼運(yùn)行時(shí)可能存在指令重排,會導(dǎo)致線程一在運(yùn)行時(shí)執(zhí)行順序是 1-->2--> 4 就賦值給instance變量了,然后接下來再執(zhí)行構(gòu)造方法初始化。問題是如果構(gòu)造方法初始化執(zhí)行沒完成前?線程二進(jìn)入發(fā)現(xiàn)instance != null,直接給線程二個(gè)半成品,加入volatile后底層會使用內(nèi)存屏障強(qiáng)制按照你以為的執(zhí)行。

    單例模式幾乎是面試必考點(diǎn),,一般有如下特性:

    懶漢式:在需要用到對象時(shí)才實(shí)例化對象,正確的實(shí)現(xiàn)方式是 Double Check + Lock + volatile,解決了并發(fā)安全和性能低下問題,對內(nèi)存要求非常高,那么使用懶漢式寫法。

    餓漢式:在類加載時(shí)已經(jīng)創(chuàng)建好該單例對象,在獲取單例對象時(shí)直接返回對象即可,對內(nèi)存要求不高使用餓漢式寫法,因?yàn)楹唵尾灰壮鲥e(cuò),且沒有任何并發(fā)安全和性能問題。

    枚舉式:Effective Java 這本書也列舉了使用枚舉,其代碼精簡,沒有線程安全問題,且?Enum?類內(nèi)部防止反射和反序列化時(shí)破壞單例。

    7、線程池

    7.1 五分鐘了解線程池

    老王是個(gè)深耕在帝都的一線碼農(nóng),辛苦一年掙了點(diǎn)錢,想把錢存儲到銀行卡里,拿錢去銀行辦理遇到了如下的遭遇

  • 老王銀行門口取號后發(fā)現(xiàn)有柜臺營業(yè)ing 但是沒人辦理業(yè)務(wù)就直接辦理了。
  • 老王取號后發(fā)現(xiàn)柜臺上都有人在辦理,等待席有空地,去坐著等辦理去了。
  • 老王取號后發(fā)現(xiàn)柜臺都有人辦理,等待席也人坐滿了,這個(gè)時(shí)候銀行經(jīng)理看到老王是老實(shí)人本著關(guān)愛老實(shí)人的態(tài)度,新開一個(gè)臨時(shí)窗口給他辦理了。
  • 老王取號后發(fā)現(xiàn)柜臺都滿了,等待座位席也滿了,臨時(shí)窗口也人滿了。這個(gè)時(shí)候銀行經(jīng)理給出了若干解決策略。
  • 直接告知人太多不給你辦理了。
  • 采用冷暴力模式,也不給不辦理也不讓他走。
  • 經(jīng)理讓老王取嘗試跟座位席中最前面的人聊一聊看是否可以加塞,可以就辦理,不可以還是被踢走。
  • 經(jīng)理直接跟老王說誰讓你來的你找誰去我這辦理不了。
  • 上面的這個(gè)流程幾乎就跟JDK線程池的大致流程類似,其中7大參數(shù):

  • 營業(yè)中的3個(gè)窗口對應(yīng)核心線程池?cái)?shù):corePoolSize
  • 銀行總的營業(yè)窗口數(shù)對應(yīng):maximumPoolSize
  • 打開的臨時(shí)窗口在多少時(shí)間內(nèi)無人辦理則關(guān)閉對應(yīng):keepAliveTime
  • 臨時(shí)窗口存貨時(shí)間單位:TimeUnit
  • 銀行里的等待座椅就是等待隊(duì)列:BlockingQueue
  • threadFactory?該參數(shù)在JDK中是 線程工廠,用來創(chuàng)建線程對象,一般不會動。
  • 無法辦理的時(shí)候銀行給出的解決方法對應(yīng):RejectedExecutionHandler
  • 當(dāng)線程池的任務(wù)緩存隊(duì)列已滿并且線程池中的線程數(shù)目達(dá)到maximumPoolSize,如果還有任務(wù)到來就會采取任務(wù)拒絕策略,一般有四大拒絕策略

  • ThreadPoolExecutor.AbortPolicy :丟棄任務(wù),并拋出?RejectedExecutionException?異常。
  • ThreadPoolExecutor.CallerRunsPolicy:該任務(wù)被線程池拒絕,由調(diào)用?execute方法的線程執(zhí)行該任務(wù)。
  • ThreadPoolExecutor.DiscardOldestPolicy :拋棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)。
  • ThreadPoolExecutor.DiscardPolicy:丟棄任務(wù),也不拋出異常。
  • 7.2 正確創(chuàng)建方式

    使用Executors創(chuàng)建線程池可能會導(dǎo)致OOM。原因在于線程池中的BlockingQueue主要有兩種實(shí)現(xiàn),分別是ArrayBlockingQueue?和?LinkedBlockingQueue

  • ArrayBlockingQueue?是一個(gè)用數(shù)組實(shí)現(xiàn)的有界阻塞隊(duì)列,必須設(shè)置容量。
  • LinkedBlockingQueue?是一個(gè)用鏈表實(shí)現(xiàn)的有界阻塞隊(duì)列,容量可以選擇進(jìn)行設(shè)置,不設(shè)置的話,將是一個(gè)無邊界的阻塞隊(duì)列,最大長度為Integer.MAX_VALUE,極易容易導(dǎo)致線程池OOM。
  • 正確創(chuàng)建線程池的方式就是自己直接調(diào)用ThreadPoolExecutor的構(gòu)造函數(shù)來自己創(chuàng)建線程池。在創(chuàng)建的同時(shí),給BlockQueue指定容量就可以了。

    private static ExecutorService executor = new ThreadPoolExecutor(10, 10,60L, TimeUnit.SECONDS,new ArrayBlockingQueue(10));

    7.3 常見線程池

    羅列幾種常見的 線程池創(chuàng)建 方式。

  • Executors.newFixedThreadPool
  • 定長的線程池,有核心線程,核心線程的即為最大的線程數(shù)量,沒有非核心線程。 使用的無界的等待隊(duì)列是LinkedBlockingQueue。使用時(shí)候小心堵滿等待隊(duì)列。

  • Executors.newSingleThreadExecutor
  • 創(chuàng)建單個(gè)線程數(shù)的線程池,它可以保證先進(jìn)先出的執(zhí)行順序

  • Executors.newCachedThreadPool
  • 創(chuàng)建一個(gè)可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

  • Executors.newScheduledThreadPool
  • 創(chuàng)建一個(gè)定長的線程池,而且支持定時(shí)的以及周期性的任務(wù)執(zhí)行,支持定時(shí)及周期性任務(wù)執(zhí)行

  • ThreadPoolExecutor
  • 最原始跟常見的創(chuàng)建線程池的方式,它包含了 7 個(gè)參數(shù)、4種拒絕策略 可用。

    7.4 線程池核心點(diǎn)

    線程池 在工作中常用,面試也是必考點(diǎn)。關(guān)于線程池的細(xì)節(jié)跟使用在以前舉例過一個(gè) 銀行排隊(duì) 辦業(yè)務(wù)的例子了。線程池一般主要也無非就是下面幾個(gè)考點(diǎn)了:

  • 為什么用線程池。
  • 線程池的作用。
  • 7大重要參數(shù)
  • 4大拒絕策略
  • 常見線程池任務(wù)隊(duì)列,如何理解有界跟無界。
  • 常用的線程池模版。
  • 如何分配線程池個(gè)數(shù),IO密集型還是?CPU密集型
  • 設(shè)定一個(gè)線程池優(yōu)先級隊(duì)列,Runable類要實(shí)現(xiàn)可對比功能,任務(wù)隊(duì)列使用優(yōu)先級隊(duì)列。
  • 8、ThreadLocal

    ThreadLocal 可以簡單理解為線程本地變量,相比于 synchronized 是用空間來換時(shí)間的思想。他會在每個(gè)線程都創(chuàng)建一個(gè)副本,在線程之間通過訪問內(nèi)部副本變量的形式做到了線程之間互相隔離。這里用到了 弱引用 知識點(diǎn):

    如果一個(gè)對象只具有弱引用,那么GC回收器在掃描到該對象時(shí),無論內(nèi)存充足與否,都會回收該對象的內(nèi)存

    8.1 核心點(diǎn)

    每個(gè)Thread內(nèi)部都維護(hù)一個(gè)ThreadLocalMap字典數(shù)據(jù)結(jié)構(gòu),字典的Key值是ThreadLocal,那么當(dāng)某個(gè)ThreadLocal對象不再使用(沒有其它地方再引用)時(shí),每個(gè)已經(jīng)關(guān)聯(lián)了此ThreadLocal的線程怎么在其內(nèi)部的ThreadLocalMap里做清除此資源呢?JDK中的ThreadLocalMap沒有繼承java.util.Map類,而是自己實(shí)現(xiàn)了一套專門用來定時(shí)清理無效資源的字典結(jié)構(gòu)。其內(nèi)部存儲實(shí)體結(jié)構(gòu)Entry<ThreadLocal, T>繼承自
    java.lan.ref.WeakReference,這樣當(dāng)ThreadLocal不再被引用時(shí),因?yàn)槿跻脵C(jī)制原因,當(dāng)jvm發(fā)現(xiàn)內(nèi)存不足時(shí),會自動回收弱引用指向的實(shí)例內(nèi)存,即其線程內(nèi)部的ThreadLocalMap會釋放其對ThreadLocal的引用從而讓jvm回收ThreadLocal對象。這里是重點(diǎn)強(qiáng)調(diào)下,回收的是Key?也就是ThreadLocal對象,而非整個(gè)Entry,所以線程變量中的值T對象還是在內(nèi)存中存在的,所以內(nèi)存泄漏的問題還沒有完全解決。

    接著分析底層代碼會發(fā)現(xiàn)在調(diào)用ThreadLocal.get() 或者 ThreadLocal.set() 都會?定期回收無效的Entry?操作。

    9、CAS

    Compare And Swap:比較并交換,主要是通過處理器的指令來保證操作的原子性,它包含三個(gè)操作數(shù):

    V:變量內(nèi)存地址

    A:舊的預(yù)期值

    B:準(zhǔn)備設(shè)置的新值

    當(dāng)執(zhí)行 CAS 指令時(shí),只有當(dāng) V 對應(yīng)的值等于 A 時(shí)才會用 B 去更新V的值,否則就不會執(zhí)行更新操作。CAS 可能會帶來ABA問題循環(huán)開銷過大問題、一個(gè)共享變量原子性操作的局限性。如何解決以前寫過,在此不再重復(fù)。

    10、Synchronized

    10.1 Synchronized 講解

    Synchronized 是 JDK自帶的線程安全關(guān)鍵字,該關(guān)鍵字可以修飾實(shí)例方法靜態(tài)方法代碼塊三部分。該關(guān)鍵字可以保證互斥性可見性有序性(不解決重排)但保證有序性

    Syn 的底層其實(shí)是C++代碼寫的,JDK6前是重量級鎖,調(diào)用的時(shí)候涉及到用戶態(tài)跟內(nèi)核態(tài)的切換,挺耗時(shí)的。JDK6之前?Doug Lea寫出了JUC包,可以方便地讓用于在用戶態(tài)實(shí)現(xiàn)鎖的使用,Syn的開發(fā)者被激發(fā)了斗志所以在JDK6后對Syn進(jìn)行了各種性能升級。

    10.2 Synchronized 底層

    Syn里涉及到了 對象頭 包含對象頭、填充數(shù)據(jù)、實(shí)例變量。這里可以看一個(gè)美團(tuán)面試題:

    問題一:new Object()占多少字節(jié)

    markword 8字節(jié) + classpointer 4字節(jié)(默認(rèn)用calssPointer壓縮) + padding 4字節(jié) =?16字節(jié)如果沒開啟classpointer壓縮:markword 8字節(jié) + classpointer 8字節(jié) =?16字節(jié)

    問題二:User (int id,String name) User u = new User(1,"李四")

    markword 8字節(jié) + 開啟classPointer壓縮后classpointer 4字節(jié) + instance data int 4字節(jié) + 開啟普通對象指針壓縮后String4字節(jié) + padding 4 =?24字節(jié)

    10.3 Synchronized 鎖升級

    synchronized 鎖在JDK6以后有四種狀態(tài),無鎖、偏向鎖、輕量級鎖、重量級鎖。這幾個(gè)狀態(tài)會隨著競爭狀態(tài)逐漸升級,鎖可以升級但不能降級,但是偏向鎖狀態(tài)可以被重置為無鎖狀態(tài)。大致升級過程如下:

    鎖對比

    鎖狀態(tài)

    優(yōu)點(diǎn)

    缺點(diǎn)

    適用場景

    偏向鎖

    加鎖解鎖無需額外消耗,跟非同步方法時(shí)間相差納秒級別

    如果競爭線程多,會帶來額外的鎖撤銷的消耗

    基本沒有其他線程競爭的同步場景

    輕量級鎖

    競爭的線程不會阻塞而是在自旋,可提高程序響應(yīng)速度

    如果一直無法獲得會自旋消耗CPU

    少量線程競爭,持有鎖時(shí)間不長,追求響應(yīng)速度

    重量級鎖

    線程競爭不會導(dǎo)致CPU自旋跟消耗CPU資源

    線程阻塞,響應(yīng)時(shí)間長

    很多線程競爭鎖,切鎖持有時(shí)間長,追求吞吐量時(shí)候

    10.4 Synchronized 無法禁止指令重排,卻能保證有序性

    指令重排是程序運(yùn)行時(shí)?解釋器?跟?CPU?自帶的加速手段,可能導(dǎo)致語句執(zhí)行順序跟預(yù)想不一樣情況,但是無論如何重排 也必須遵循?as-if-serial

    避免重排的最簡單方法就是禁止處理器優(yōu)化跟指令重排,比如 volatile 中用內(nèi)存屏障實(shí)現(xiàn),syn是關(guān)鍵字級別的排他且可重入鎖,當(dāng)某個(gè)線程執(zhí)行到一段被syn修飾的代碼之前,會先進(jìn)行加鎖,執(zhí)行完之后再進(jìn)行解鎖。

    當(dāng)某段代碼被syn加鎖后跟解鎖前,其他線程是無法再次獲得鎖的,只有這條加鎖線程可以重復(fù)獲得該鎖。所以代碼在執(zhí)行的時(shí)候是單線程執(zhí)行的,這就滿足了as-if-serial語義,正是因?yàn)橛辛?strong>as-if-serial語義保證,單線程的有序性就天然存在了。

    10.5 wait 虛假喚醒

    虛假喚醒定義:

    當(dāng)一個(gè)條件滿足時(shí),很多線程都被喚醒了,但只有其中部分是有用的喚醒,其它的喚醒是不對的,比如說買賣貨物,如果商品本來沒有貨物,所有消費(fèi)者線程都在wait狀態(tài)卡頓呢。這時(shí)突然生產(chǎn)者進(jìn)了一件商品,喚醒了所有掛起的消費(fèi)者。可能導(dǎo)致所有的消費(fèi)者都繼續(xù)執(zhí)行wait下面的代碼,出現(xiàn)錯(cuò)誤調(diào)用。

    虛假喚醒原因:

    因?yàn)?if?只會執(zhí)行一次,執(zhí)行完會接著向下執(zhí)行?if?下面的。而?while?不會,直到條件滿足才會向下執(zhí)行?while下面的。

    虛假喚醒 解決辦法:

    在調(diào)用?wait?的時(shí)候要用?while?不能用?if

    10.6 notify()底層

  • 為何waitnotify必須要加synchronized
  • synchronized?代碼塊通過?javap?生成的字節(jié)碼中包含monitorenter?和monitorexit?指令線程,執(zhí)行?monitorenter?指令可以獲取對象的?monitor,而wait?方法通過調(diào)用?native?方法?wait(0)?實(shí)現(xiàn),該注釋說:The current thread must own this object's monitor

  • notify?執(zhí)行后立馬喚醒線程嗎?
  • notify/notifyAll?調(diào)用時(shí)并不會真正釋放對象鎖,只是把等待中的線程喚醒然后放入到對象的鎖池中,但是鎖池中的所有線程都不會立馬運(yùn)行,只有擁有鎖的線程運(yùn)行完代碼塊釋放鎖,別的線程拿到鎖才可以運(yùn)行。

    public void test() {Object object = new Object();synchronized (object){object.notifyAll();while (true){// TODO 死循環(huán)會導(dǎo)致 無法釋放鎖。}} }

    11、AQS

    11.1 高頻考點(diǎn)線程交替打印

    目標(biāo)是實(shí)現(xiàn)兩個(gè)線程交替打印,實(shí)現(xiàn)字母在前數(shù)字在后。你可以用信號量、Synchronized關(guān)鍵字跟Lock實(shí)現(xiàn),這里用 ReentrantLock簡單實(shí)現(xiàn):

    import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class Main {private static Lock lock = new ReentrantLock();private static Condition c1 = lock.newCondition();private static Condition c2 = lock.newCondition();private static CountDownLatch count = new CountDownLatch(1);public static void main(String[] args) {String c = "ABCDEFGHI";char[] ca = c.toCharArray();String n = "123456789";char[] na = n.toCharArray();Thread t1 = new Thread(() -> {try {lock.lock();count.countDown();for(char caa : ca) {c1.signal();System.out.print(caa);c2.await();}c1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});Thread t2 = new Thread(() -> {try {count.await();lock.lock();for(char naa : na) {c2.signal();System.out.print(naa);c1.await();}c2.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});t1.start(); t2.start();} }

    11.2 AQS底層

    上題我們用到了ReentrantLockCondition?,但是它們的底層是如何實(shí)現(xiàn)的呢?其實(shí)他們是基于AQS 的?同步隊(duì)列?跟?等待隊(duì)列?實(shí)現(xiàn)的!

    11.2.1 AQS 同步隊(duì)列

    學(xué) AQS 前?CAS?+?自旋?+?LockSupport?+?模板模式?必須會,目的是方便理解源碼,感覺比 Synchronized 簡單,因?yàn)槭菃渭兊?Java?代碼。個(gè)人理解 AQS 具有如下幾個(gè)特點(diǎn):

  • 在AQS 同步隊(duì)列中 -1 表示線程在睡眠狀態(tài)
  • 當(dāng)前Node節(jié)點(diǎn)線程會把前一個(gè)Node.ws = -1。當(dāng)前節(jié)點(diǎn)把前面節(jié)點(diǎn)ws設(shè)置為-1,你可以理解為:你自己能知道自己睡著了嗎?只能是別人看到了發(fā)現(xiàn)你睡眠了!
  • 持有鎖的線程永遠(yuǎn)不在隊(duì)列中。
  • 在AQS隊(duì)列中第二個(gè)才是最先排隊(duì)的線程。
  • 如果是交替型任務(wù)或者單線程任務(wù),即使用了Lock也不會涉及到AQS 隊(duì)列。
  • 不到萬不得已不要輕易park線程,很耗時(shí)的!所以排隊(duì)的頭線程會自旋的嘗試幾個(gè)獲取鎖。
  • 并不是說 CAS 一定比SYN好,如果高并發(fā)執(zhí)行時(shí)間久 ,用SYN好, 因?yàn)镾YN底層用了wait() 阻塞后是不消耗CPU資源的。如果鎖競爭不激烈說明自旋不嚴(yán)重 此時(shí)用CAS。
  • 在AQS中也要盡可能避免調(diào)用CLH隊(duì)列,因?yàn)镃LH可能會調(diào)用到park,相對來耗時(shí)。
  • ReentrantLock底層

    11.2.2 AQS 等待隊(duì)列

    當(dāng)我們調(diào)用 Condition 里的?await?跟?signal?時(shí)候底層其實(shí)是這樣走的。

    12、線程思考

    12.1. 變量建議使用棧封閉

    所有的變量都是在方法內(nèi)部聲明的,這些變量都處于棧封閉狀態(tài)。方法調(diào)用的時(shí)候會有一個(gè)棧楨,這是一個(gè)獨(dú)立的空間。在這個(gè)獨(dú)立空間創(chuàng)建跟使用則絕對是安全的,但是注意不要返回該變量哦!

    12.2. 防止線程饑餓

    優(yōu)先級低的線程總是得不到執(zhí)行機(jī)會,一般要保證資源充足、公平的分配資源、防止持有鎖的線程長時(shí)間執(zhí)行。

    12.3 開發(fā)步驟

    多線程編程不要為了用而用,引入多線程后會引入額外的開銷。量應(yīng)用程序性能一般:服務(wù)時(shí)間、延遲時(shí)間、吞吐量、可伸縮性。做應(yīng)用的時(shí)候可以一般按照如下步驟:

  • 先確保保證程序的正確性跟健壯性,確實(shí)達(dá)不到性能要求再想如何提速。
  • 一定要以測試為基準(zhǔn)。
  • 一個(gè)程序中串行的部分永遠(yuǎn)是有的.
  • 裝逼利器:阿姆達(dá)爾定律?S=1/(1-a+a/n)
  • 阿姆達(dá)爾定律中 a為并行計(jì)算部分所占比例,n為并行處理結(jié)點(diǎn)個(gè)數(shù):

  • 當(dāng)1-a=0時(shí),(即沒有串行,只有并行)最大加速比s=n;
  • 當(dāng)a=0時(shí)(即只有串行,沒有并行),最小加速比s=1;
  • 當(dāng)n無窮大時(shí),極限加速比s→ 1/(1-a),這就是加速比的上限。例如,若串行代碼占整個(gè)代碼的25%,則并行處理的總體性能不可能超過4。
  • 12.4 影響性能因素

  • 縮小鎖的范圍,能鎖方法塊盡量不要鎖函數(shù)
  • 減少鎖的粒度跟鎖分段,比如ConcurrentHashMap的實(shí)現(xiàn)。
  • 讀多寫少時(shí)候用讀寫鎖,可提高十倍性能。
  • 用CAS操作來替換重型鎖。
  • 盡量用JDK自帶的常見并發(fā)容器,底層已經(jīng)足夠優(yōu)化了。
  • 13、End

    都看到這了,送你幾個(gè)高頻面試題吧。

  • synchronizedReentrantLock使用區(qū)別跟底層實(shí)現(xiàn)以及重入底層原理
  • 描述下鎖的四種狀態(tài)跟升級過程
  • CAS是什么?CAS的弊端是什么?
  • 你對volatile的理解,可見性跟指令重排咋實(shí)現(xiàn)的。
  • 一個(gè)對象創(chuàng)建過程是怎么樣的。對象在內(nèi)存中如何分布的,看 JVM 即可。
  • 聊一聊單例模式,為什么DCL要用volatile
  • Object 0 = new Object() 在內(nèi)存中占據(jù)幾個(gè)字節(jié)
  • 你對as-if-serialhapppends-before的理解
  • ThreadLocal說一說,咋解決內(nèi)存泄露
  • 自旋鎖一定比重量級鎖效率高嗎?偏向鎖是否效率一定提高。
  • 線程池聊一聊如何用 注意細(xì)節(jié),如何實(shí)現(xiàn)。
  • 你對JMM理解?
  • Synchronized?可以實(shí)現(xiàn)指令重排么?它是如何保證有序性的?
  • 聊一聊AQS,為什么?AQS?底層是?CAS?+?Volatile
  • 總結(jié)

    以上是生活随笔為你收集整理的吃透这JAVA并发十二核心,面试官都得对你刮目相看的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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