開篇介紹面試
你們好,公衆號【Java極客思惟】近期會整理一些Java高頻面試題分享給小夥伴,也但願看到的小夥伴在找工做過程當中可以用獲得!本章節主要針對Java一些多線程高頻面試題進行分享。算法
通知:公衆號【Java極客思惟】正在送書福利活動,關注公衆號並參加福利活動吧!只有參與了本次活動的小夥伴纔可以參與年末的大福利,不要錯過呀~緩存
Q1:微信
什麼是CAS算法?數據結構
CAS(compare and swap)的縮寫。多線程
Java利用CPU的CAS指令,同時藉助JNI來完成對Java的非阻塞算法,實現原子操做(其實就是自旋操做,不斷循環,直到成功)。其它原子操做都是利用相似的特性來完成的。併發
CAS有三個關鍵操做值:內存值V、 預期值A 、 要修改的值B 。app
當且僅當 預期值A 和 內存值V 一致時,纔會將 內存值V 內容修改成 B,不然將什麼都不作。框架
CAS的缺點也很明顯:工具
在併發量比較高的狀況下,若是許多線程反覆嘗試更新某一個變量,可是卻又一直更新不成功,一直在循環(自旋),那麼會給CPU帶來很大的壓力。
CAS機制所保證的只是一個變量的原子性操做,而不能保證整個代碼塊的原子性。好比須要保證3個變量共同進行原子性的更新,這樣就不得不使用synchronized關鍵字進行同步操做了。
好比線程A端了一杯水放在桌子上,可是被其餘事調度走了,而且釋放了鎖,此時線程B通過,看到桌子上的水,端起來喝了半杯,而後又給打滿一杯水放在桌子上。此時雖然仍是一杯水,可是杯中的水再也不是原來的那杯水了,而線程A也忙完了,回頭來看到桌子上仍是一杯水,可是不知道水已經被替換過了。這就是典型的ABA問題,還有不少相似的場景。這種狀況對依賴過程值的情景的運算結果影響很大。這是CAS機制最大的問題所在。
CPU開銷過大
不能保證代碼塊的原子性
ABA問題
Q2:
什麼是AQS?
AQS(AbstractQueuedSynchronizer)
AQS是JDK下提供的一套用於實現基於FIFO等待隊列的阻塞鎖和相關的同步器的一個同步框架。這個抽象類被設計做爲一些可用原子int值來表示狀態的同步器的基類。
若是有看過相似CountDownLatch類的源碼實現,會發現其內部有一個繼承了AbstractQueuedSynchronizer的內部類Sync。可見CountDownLatch是基於AQS框架來實現的一個同步器。相似的同步器在JUC下還有很多(好比Semaphore)。
AQS的核心思想是基於volatile int state 這樣的volatile變量,配合Unsafe工具對其原子性的操做來實現對當前鎖狀態進行修改。同步器內部依賴一個FIFO的雙向隊列來完成資源獲取線程的排隊工做。
AQS中的數據結構 - 節點和同步隊列
節點加入到同步隊列
首節點變化
Q3:
volatile關鍵字有什麼用?
Java提供了volatile關鍵字來保證可見性。當一個共享變量被volatile修飾時,它會保證修改的值會當即被更新到主內存中,當有其餘線程須要讀取時,它會去內存中讀取新值。主要的原理是使用了內存指令。
LoadLoad重排序:一個處理器先執行一個L1讀操做,再執行一個L2讀操做;可是另一個處理器看到的是先L2再L1;
StoreStore重排序:一個處理器先執行一個W1寫操做,再執行一個W2寫操做;可是另一個處理器看到的是先W2再W1;
LoadStore重排序:一個處理器先執行一個L1讀操做,再執行一個W2寫操做;可是另一個處理器看到的是先W2再L1;
StoreLoad重排序:一個處理器先執行一個W1寫操做,再執行一個L2讀操做;可是另一個處理器看到的是先L2再W1。
Q4:
描述一下volatile關鍵字對原子性、可見性以及有序性是如何保證的?
在volatile變量寫操做的前面會加入一個Release屏障,而後再以後會加入一個Store屏障,這樣就能夠保證volatile寫跟Release屏障以前的任何讀寫操做都不會指令重排,而後Store屏障保證了,寫完數據以後,立馬會執行flush處理器緩存的操做。
在volatile變量讀操做的前面會加入一個Load屏障,這樣就能夠保證對這個變量的讀取時,若是被別的處理器修改過了,必須得從其餘處理器的高速緩存(或者主內存)中加載到本身本地高速緩存裏,保證讀到的是最新數據;在以後會加入一個Acquire屏障,禁止volatile讀操做以後的任何讀寫操做會跟volatile讀指令重排序。
與volatile讀寫內存屏障對比一下,是相似的意思。
Acquire屏障 其實就是 LoadLoad屏障 + LoadStore屏障;
Release屏障 其實就是 StoreLoad屏障 + StoreStore屏障。
Q5:
簡述一下synchronized關鍵字的原理是什麼?
synchronized是由JVM實現的一種實現互斥同步的方式,查看被synchronized關鍵字修飾過的程序塊編譯後的字節碼會發現:被synchronized修飾過的程序塊,在編譯先後被編譯器生成了monitorenter 和 monitorexit 兩個字節碼指令。
在虛擬機執行到 monitorenter 指令時,首先會嘗試獲取對象的鎖:若是這個對象沒有鎖定,或者當前線程已經擁有了這個對象的鎖,把鎖的計數器 + 1;
當執行 monitorexit 指令時,將鎖計數器 - 1;當計數器爲0時,鎖就被釋放了。若是獲取對象失敗了,那當前線程就要阻塞等待,知道對象鎖被另一個線程釋放爲止。
Q6:
CountDownLatch 和 CyclicBarrier的區別?
CountDownLatch的計數器只能使用一次。而CyclicBarrierd的計數器可使用reset()方法進行重置。因此CyclicBarrier能處理更爲複雜的業務場景。好比若是計算髮生錯誤,能夠重置計數器,並讓線程們從新計算一次。
CyclicBarrier還提供其餘有用的方法,好比getNumberWaiting()方法能夠得到CyclicBarrier阻塞的線程數量。isBroken() 方法能夠用來知道阻塞的線程是否被中斷。
CountDownLatch會阻塞主線程,CyclicBarrier不會阻塞主線程,只會阻塞子線程。
CountDownLatch依靠一個外力(計數器、發令槍)來控制線程,而CyclicBarrier是至關於用自己來控制線程。舉個例子:
CountDownLatch:有一個裝滿寶石的房間,門外有7把鎖,而後有7我的要進入房間組成隊伍A,還有另外7我的手裏拿着鑰匙組成隊伍B,那麼首先A組的7我的必須等到B組的7我的把鑰匙送過來,而後把門外的7把鎖分別打開以後,這A組的7我的纔可以進入房間拿到寶石。
CyclicBarrier:有一個裝滿寶石的房間,門外也有7把鎖,而後7我的必須到另外的一個房間,各自完成一個任務以後,這7我的纔可以各自得到一把鎖,完成任務以後,這7我的就可以打開那7把鎖,進房間拿到寶石。
明天,會介紹多線程一些深刻的知識,長按二維碼關注我吧~
祝你們都能拿到心儀的offer!
若是以爲文章不錯,歡迎關注、點贊、收藏,大家的支持是我創做的動力,感謝你們。
若是文章寫的有問題,請不要吝嗇,歡迎留言指出,我會及時覈查修改。
若是你還想更加深刻的瞭解我,能夠微信搜索「Java極客思惟」進行關注。天天8:00準時推送技術文章,讓你的上班路不在孤獨,並且每個月還有送書活動,助你提高硬實力!