Java并發(fā)編程八股文(背誦版)
前幾天,二哥整理了一份Java 基礎篇的八股文,很多小伙伴都留言說太干貨了,正好需要,還有要 PDF 版的。

我回復說等我再增加點內容,這不,今天給大家整理了一份 Java 并發(fā)編程篇的八股文,大家在面試前背一波,不管是秋招還是金九銀十的跳槽,稍微“吊打”一下面試官我覺得還是很舒服的。

簡述Java內存模型(JMM)
Java內存模型定義了程序中各種變量的訪問規(guī)則:
所有變量都存儲在主存,每個線程都有自己的工作內存。 工作內存中保存了被該線程使用的變量的主存副本,線程對變量的所有操作都必須在工作空間進行,不能直接讀寫主內存數(shù)據(jù)。 操作完成后,線程的工作內存通過緩存一致性協(xié)議將操作完的數(shù)據(jù)刷回主存。
簡述as-if-serial
編譯器會對原始的程序進行指令重排序和優(yōu)化。但不管怎么重排序,其結果都必須和用戶原始程序輸出的預定結果保持一致。
簡述happens-before八大規(guī)則
程序次序規(guī)則:一個線程內,按照代碼順序,書寫在前面的操作先行發(fā)生于書寫在后面的操作; 鎖定規(guī)則:一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作; volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作; 傳遞規(guī)則:如果操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C; 線程啟動規(guī)則:Thread對象的start()方法先行發(fā)生于此線程的每個一個動作; 線程中斷規(guī)則:對線程interrupt()方法的調用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生; 線程終結規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行; 對象終結規(guī)則:一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始;
as-if-serial 和 happens-before 的區(qū)別
as-if-serial 保證單線程程序的執(zhí)行結果不變,happens-before 保證正確同步的多線程程序的執(zhí)行結果不變。
簡述原子性操作
一個操作或者多個操作,要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行,這就是原子性操作。
簡述線程的可見性
可見性指當一個線程修改了共享變量時,其他線程能夠立即得知修改。volatile、synchronized、final 關鍵字都能保證可見性。
簡述有序性
雖然多線程存在并發(fā)和指令優(yōu)化等操作,但在本線程內觀察該線程的所有執(zhí)行操作是有序的。
簡述Java中volatile關鍵字作用
保證變量對所有線程的可見性。當一個線程修改了變量值,新值對于其他線程來說是立即可以得知的。 禁止指令重排。使用 volatile 變量進行寫操作,編譯器在生成字節(jié)碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器進行重排序。
Java線程的實現(xiàn)方式
實現(xiàn)Runnable接口 繼承Thread類 實現(xiàn)Callable接口
簡述Java線程的狀態(tài)
線程狀態(tài)有 NEW、RUNNABLE、BLOCK、WAITING、TIMED_WAITING、THERMINATED
NEW:新建狀態(tài),線程被創(chuàng)建且未啟動,此時還未調用 start 方法。 RUNNABLE:運行狀態(tài)。表示線程正在JVM中執(zhí)行,但是這個執(zhí)行,不一定真的在跑,也可能在排隊等CPU。 BLOCKED:阻塞狀態(tài)。線程等待獲取鎖,鎖還沒獲得。 WAITING:等待狀態(tài)。線程內run方法執(zhí)行完Object.wait()/Thread.join()進入該狀態(tài)。 TIMED_WAITING:限期等待。在一定時間之后跳出狀態(tài)。調用Thread.sleep(long) Object.wait(long) Thread.join(long)進入狀態(tài)。其中這些參數(shù)代表等待的時間。 TERMINATED:結束狀態(tài)。線程調用完run方法進入該狀態(tài)。
簡述線程通信的方式
volatile 關鍵詞修飾變量,保證所有線程對變量訪問的可見性。 synchronized關鍵詞。確保多個線程在同一時刻只能有一個處于方法或同步塊中。 wait/notify方法 IO通信
簡述線程池
沒有線程池的情況下,多次創(chuàng)建,銷毀線程開銷比較大。如果在開辟的線程執(zhí)行完當前任務后復用已創(chuàng)建的線程,可以降低開銷、控制最大并發(fā)數(shù)。
線程池創(chuàng)建線程時,會將線程封裝成工作線程 Worker,Worker 在執(zhí)行完任務后還會循環(huán)獲取工作隊列中的任務來執(zhí)行。
將任務派發(fā)給線程池時,會出現(xiàn)以下幾種情況
核心線程池未滿,創(chuàng)建一個新的線程執(zhí)行任務。 如果核心線程池已滿,工作隊列未滿,將線程存儲在工作隊列。 如果工作隊列已滿,線程數(shù)小于最大線程數(shù)就創(chuàng)建一個新線程處理任務。 如果超過大小線程數(shù),按照拒絕策略來處理任務。
線程池參數(shù):
corePoolSize:常駐核心線程數(shù)。超過該值后如果線程空閑會被銷毀。 maximumPoolSize:線程池能夠容納同時執(zhí)行的線程最大數(shù)。 keepAliveTime:線程空閑時間,線程空閑時間達到該值后會被銷毀,直到只剩下 corePoolSize 個線程為止,避免浪費內存資源。 workQueue:工作隊列。 threadFactory:線程工廠,用來生產(chǎn)一組相同任務的線程。 handler:拒絕策略。
拒絕策略有以下幾種:
AbortPolicy:丟棄任務并拋出異常 CallerRunsPolicy:重新嘗試提交該任務 DiscardOldestPolicy 拋棄隊列里等待最久的任務并把當前任務加入隊列 DiscardPolicy 表示直接拋棄當前任務但不拋出異常。
簡述Executor框架
Executor框架目的是將任務提交和任務如何運行分離開來的機制。用戶不再需要從代碼層考慮設計任務的提交運行,只需要調用Executor框架實現(xiàn)類的Execute方法就可以提交任務。
簡述Executor的繼承關系
Executor:一個接口,其定義了一個接收Runnable對象的方法executor,該方法接收一個Runable實例執(zhí)行這個任務。 ExecutorService:Executor的子類接口,其定義了一個接收Callable對象的方法,返回 Future 對象,同時提供execute方法。 ScheduledExecutorService:ExecutorService的子類接口,支持定期執(zhí)行任務。 AbstractExecutorService:抽象類,提供 ExecutorService 執(zhí)行方法的默認實現(xiàn)。 Executors:實現(xiàn)ExecutorService接口的靜態(tài)工廠類,提供了一系列工廠方法用于創(chuàng)建線程池。 ThreadPoolExecutor:繼承AbstractExecutorService,用于創(chuàng)建線程池。 ForkJoinPool: 繼承AbstractExecutorService,F(xiàn)ork 將大任務分叉為多個小任務,然后讓小任務執(zhí)行,Join 是獲得小任務的結果,類似于map reduce。 ThreadPoolExecutor:繼承ThreadPoolExecutor,實現(xiàn)ScheduledExecutorService,用于創(chuàng)建帶定時任務的線程池。
簡述線程池的狀態(tài)
Running:能接受新提交的任務,也可以處理阻塞隊列的任務。 Shutdown:不再接受新提交的任務,但可以處理存量任務,線程池處于running時調用shutdown方法,會進入該狀態(tài)。 Stop:不接受新任務,不處理存量任務,調用shutdownnow進入該狀態(tài)。 Tidying:所有任務已經(jīng)終止了,worker_count(有效線程數(shù))為0。 Terminated:線程池徹底終止。在tidying模式下調用terminated方法會進入該狀態(tài)。
簡述線程池類型
newCachedThreadPool 可緩存線程池,可設置最小線程數(shù)和最大線程數(shù),線程空閑1分鐘后自動銷毀。 newFixedThreadPool 指定工作線程數(shù)量線程池。 newSingleThreadExecutor 單線程Executor。 newScheduleThreadPool 支持定時任務的指定工作線程數(shù)量線程池。 newSingleThreadScheduledExecutor 支持定時任務的單線程Executor。
簡述阻塞隊列
阻塞隊列是生產(chǎn)者消費者的實現(xiàn)具體組件之一。當阻塞隊列為空時,從隊列中獲取元素的操作將會被阻塞,當阻塞隊列滿了,往隊列添加元素的操作將會被阻塞。具體實現(xiàn)有:
ArrayBlockingQueue:底層是由數(shù)組組成的有界阻塞隊列。 LinkedBlockingQueue:底層是由鏈表組成的有界阻塞隊列。 PriorityBlockingQueue:阻塞優(yōu)先隊列。 DelayQueue:創(chuàng)建元素時可以指定多久才能從隊列中獲取當前元素 SynchronousQueue:不存儲元素的阻塞隊列,每一個存儲必須等待一個取出操作 LinkedTransferQueue:與LinkedBlockingQueue相比多一個transfer方法,即如果當前有消費者正等待接收元素,可以把生產(chǎn)者傳入的元素立刻傳輸給消費者。 LinkedBlockingDeque:雙向阻塞隊列。
談一談ThreadLocal
ThreadLocal 是線程共享變量。ThreadLoacl 有一個靜態(tài)內部類 ThreadLocalMap,其 Key 是 ThreadLocal 對象,值是 Entry 對象,ThreadLocalMap是每個線程私有的。
set 給ThreadLocalMap設置值。 get 獲取ThreadLocalMap。 remove 刪除ThreadLocalMap類型的對象。
存在的問題:對于線程池,由于線程池會重用 Thread 對象,因此與 Thread 綁定的 ThreadLocal 也會被重用,造成一系列問題。
比如說內存泄漏。由于 ThreadLocal 是弱引用,但 Entry 的 value 是強引用,因此當 ThreadLocal 被垃圾回收后,value 依舊不會被釋放,產(chǎn)生內存泄漏。
聊聊你對Java并發(fā)包下unsafe類的理解
對于 Java 語言,沒有直接的指針組件,一般也不能使用偏移量對某塊內存進行操作。這些操作相對來講是安全(safe)的。
Java 有個類叫 Unsafe 類,這個類使 Java 擁有了像 C 語言的指針一樣操作內存空間的能力,同時也帶來了指針的問題。這個類可以說是 Java 并發(fā)開發(fā)的基礎。
Java中的樂觀鎖與CAS算法
樂觀鎖認為數(shù)據(jù)發(fā)送時發(fā)生并發(fā)沖突的概率不大,所以讀操作前不上鎖。
到了寫操作時才會進行判斷,數(shù)據(jù)在此期間是否被其他線程修改。如果發(fā)生修改,那就返回寫入失敗;如果沒有被修改,那就執(zhí)行修改操作,返回修改成功。
樂觀鎖一般都采用 Compare And Swap(CAS)算法進行實現(xiàn)。顧名思義,該算法涉及到了兩個操作,比較(Compare)和交換(Swap)。
CAS 算法的思路如下:
該算法認為不同線程對變量的操作時產(chǎn)生競爭的情況比較少。 該算法的核心是對當前讀取變量值 E 和內存中的變量舊值 V 進行比較。 如果相等,就代表其他線程沒有對該變量進行修改,就將變量值更新為新值 N。 如果不等,就認為在讀取值 E 到比較階段,有其他線程對變量進行過修改,不進行任何操作。
ABA問題及解決方法簡述
CAS 算法是基于值來做比較的,如果當前有兩個線程,一個線程將變量值從 A 改為 B ,再由 B 改回為 A ,當前線程開始執(zhí)行 CAS 算法時,就很容易認為值沒有變化,誤認為讀取數(shù)據(jù)到執(zhí)行 CAS 算法的期間,沒有線程修改過數(shù)據(jù)。
juc 包提供了一個 AtomicStampedReference,即在原始的版本下加入版本號戳,解決 ABA 問題。
簡述常見的Atomic類
在很多時候,我們需要的僅僅是一個簡單的、高效的、線程安全的++或者--方案,使用synchronized關鍵字和lock固然可以實現(xiàn),但代價比較大,此時用原子類更加方便。基本數(shù)據(jù)類型的原子類有:
AtomicInteger 原子更新整形 AtomicLong 原子更新長整型 AtomicBoolean 原子更新布爾類型
Atomic數(shù)組類型有:
AtomicIntegerArray 原子更新整形數(shù)組里的元素 AtomicLongArray 原子更新長整型數(shù)組里的元素 AtomicReferenceArray 原子更新引用類型數(shù)組里的元素。
Atomic引用類型有:
AtomicReference 原子更新引用類型 AtomicMarkableReference 原子更新帶有標記位的引用類型,可以綁定一個 boolean 標記 AtomicStampedReference 原子更新帶有版本號的引用類型
FieldUpdater類型:
AtomicIntegerFieldUpdater 原子更新整形字段的更新器 AtomicLongFieldUpdater 原子更新長整形字段的更新器 AtomicReferenceFieldUpdater 原子更新引用類型字段的更新器
簡述Atomic類基本實現(xiàn)原理
以AtomicIntger 為例。
方法getAndIncrement,以原子方式將當前的值加1,具體實現(xiàn)為:
在 for 死循環(huán)中取得 AtomicInteger 里存儲的數(shù)值 對 AtomicInteger 當前的值加 1 調用 compareAndSet 方法進行原子更新 先檢查當前數(shù)值是否等于 expect 如果等于則說明當前值沒有被其他線程修改,則將值更新為 next, 如果不是會更新失敗返回 false,程序會進入 for 循環(huán)重新進行 compareAndSet 操作。
簡述CountDownLatch
CountDownLatch這個類使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始值是線程的數(shù)量。每當一個線程執(zhí)行完畢后,調用countDown方法,計數(shù)器的值就減1,當計數(shù)器的值為0時,表示所有線程都執(zhí)行完畢,然后在等待的線程就可以恢復工作了。只能一次性使用,不能reset。
簡述CyclicBarrier
CyclicBarrier 主要功能和CountDownLatch類似,也是通過一個計數(shù)器,使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。但是其可以重復使用(reset)。
簡述Semaphore
Semaphore即信號量。Semaphore 的構造方法參數(shù)接收一個 int 值,設置一個計數(shù)器,表示可用的許可數(shù)量即最大并發(fā)數(shù)。使用 acquire 方法獲得一個許可證,計數(shù)器減一,使用 release 方法歸還許可,計數(shù)器加一。如果此時計數(shù)器值為0,線程進入休眠。
簡述Exchanger
Exchanger類可用于兩個線程之間交換信息。可簡單地將Exchanger對象理解為一個包含兩個格子的容器,通過exchanger方法可以向兩個格子中填充信息。線程通過exchange 方法交換數(shù)據(jù),第一個線程執(zhí)行 exchange 方法后會阻塞等待第二個線程執(zhí)行該方法。當兩個線程都到達同步點時這兩個線程就可以交換數(shù)據(jù)當兩個格子中的均被填充時,該對象會自動將兩個格子的信息交換,然后返回給線程,從而實現(xiàn)兩個線程的信息交換。
簡述ConcurrentHashMap
JDK7采用鎖分段技術。首先將數(shù)據(jù)分成 Segment 數(shù)據(jù)段,然后給每一個數(shù)據(jù)段配一把鎖,當一個線程占用鎖訪問其中一個段的數(shù)據(jù)時,其他段的數(shù)據(jù)也能被其他線程訪問。
get 除讀到空值不需要加鎖。該方法先經(jīng)過一次再散列,再用這個散列值通過散列運算定位到 Segment,最后通過散列算法定位到元素。put 須加鎖,首先定位到 Segment,然后進行插入操作,第一步判斷是否需要對 Segment 里的 HashEntry 數(shù)組進行擴容,第二步定位添加元素的位置,然后將其放入數(shù)組。
JDK8的改進
取消分段鎖機制,采用CAS算法進行值的設置,如果CAS失敗再使用 synchronized 加鎖添加元素 引入紅黑樹結構,當某個槽內的元素個數(shù)超過8且 Node數(shù)組 容量大于 64 時,鏈表轉為紅黑樹。 使用了更加優(yōu)化的方式統(tǒng)計集合內的元素數(shù)量。
synchronized底層實現(xiàn)原理
Java 對象底層都會關聯(lián)一個 monitor,使用 synchronized 時 JVM 會根據(jù)使用環(huán)境找到對象的 monitor,根據(jù) monitor 的狀態(tài)進行加解鎖的判斷。如果成功加鎖就成為該 monitor 的唯一持有者,monitor 在被釋放前不能再被其他線程獲取。
synchronized在JVM編譯后會產(chǎn)生monitorenter 和 monitorexit 這兩個字節(jié)碼指令,獲取和釋放 monitor。這兩個字節(jié)碼指令都需要一個引用類型的參數(shù)指明要鎖定和解鎖的對象,對于同步普通方法,鎖是當前實例對象;對于靜態(tài)同步方法,鎖是當前類的 Class 對象;對于同步方法塊,鎖是 synchronized 括號里的對象。
執(zhí)行 monitorenter 指令時,首先嘗試獲取對象鎖。如果這個對象沒有被鎖定,或當前線程已經(jīng)持有鎖,就把鎖的計數(shù)器加 1,執(zhí)行 monitorexit 指令時會將鎖計數(shù)器減 1。一旦計數(shù)器為 0 鎖隨即就被釋放。
synchronized關鍵詞使用方法
直接修飾某個實例方法 直接修飾某個靜態(tài)方法 修飾代碼塊
簡述Java偏向鎖
JDK 1.6 中提出了偏向鎖的概念。該鎖提出的原因是,開發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競爭,一把鎖往往是由同一個線程獲得的。偏向鎖并不會主動釋放,這樣每次偏向鎖進入的時候都會判斷該資源是否是偏向自己的,如果是偏向自己的則不需要進行額外的操作,直接可以進入同步操作。
其申請流程為:
首先需要判斷對象的 Mark Word 是否屬于偏向模式,如果不屬于,那就進入輕量級鎖判斷邏輯。否則繼續(xù)下一步判斷; 判斷目前請求鎖的線程 ID 是否和偏向鎖本身記錄的線程 ID 一致。如果一致,繼續(xù)下一步的判斷,如果不一致,跳轉到步驟4; 判斷是否需要重偏向。如果不用的話,直接獲得偏向鎖; 利用 CAS 算法將對象的 Mark Word 進行更改,使線程 ID 部分換成本線程 ID。如果更換成功,則重偏向完成,獲得偏向鎖。如果失敗,則說明有多線程競爭,升級為輕量級鎖。
簡述輕量級鎖
輕量級鎖是為了在沒有競爭的前提下減少重量級鎖出現(xiàn)并導致的性能消耗。
其申請流程為:
如果同步對象沒有被鎖定,虛擬機將在當前線程的棧幀中建立一個鎖記錄空間,存儲鎖對象目前 Mark Word 的拷貝。 虛擬機使用 CAS 嘗試把對象的 Mark Word 更新為指向鎖記錄的指針 如果更新成功即代表該線程擁有了鎖,鎖標志位將轉變?yōu)?00,表示處于輕量級鎖定狀態(tài)。 如果更新失敗就意味著至少存在一條線程與當前線程競爭。虛擬機檢查對象的 Mark Word 是否指向當前線程的棧幀 如果指向當前線程的棧幀,說明當前線程已經(jīng)擁有了鎖,直接進入同步塊繼續(xù)執(zhí)行 如果不是則說明鎖對象已經(jīng)被其他線程搶占。 如果出現(xiàn)兩條以上線程爭用同一個鎖,輕量級鎖就不再有效,將膨脹為重量級鎖,鎖標志狀態(tài)變?yōu)?10,此時Mark Word 存儲的就是指向重量級鎖的指針,后面等待鎖的線程也必須阻塞。
簡述鎖優(yōu)化策略
即自適應自旋、鎖消除、鎖粗化、鎖升級等策略偏。
簡述Java的自旋鎖
線程獲取鎖失敗后,可以采用這樣的策略,可以不放棄 CPU ,不停的重試內重試,這種操作也稱為自旋鎖。
簡述自適應自旋鎖
自適應自旋鎖自旋次數(shù)不再人為設定,通常由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)決定。
簡述鎖粗化
鎖粗化的思想就是擴大加鎖范圍,避免反復的加鎖和解鎖。
簡述鎖消除
鎖消除是一種更為徹底的優(yōu)化,在編譯時,Java編譯器對運行上下文進行掃描,去除不可能存在共享資源競爭的鎖。
簡述Lock與ReentrantLock
Lock接口是 Java并發(fā)包的頂層接口。
可重入鎖 ReentrantLock 是 Lock 最常見的實現(xiàn),與 synchronized 一樣可重入。ReentrantLock 在默認情況下是非公平的,可以通過構造方法指定公平鎖。一旦使用了公平鎖,性能會下降。
簡述AQS
AQS(AbstractQuenedSynchronizer)抽象的隊列式同步器。AQS是將每一條請求共享資源的線程封裝成一個鎖隊列的一個結點(Node),來實現(xiàn)鎖的分配。AQS是用來構建鎖或其他同步組件的基礎框架,它使用一個 volatile int state 變量作為共享資源,如果線程獲取資源失敗,則進入同步隊列等待;如果獲取成功就執(zhí)行臨界區(qū)代碼,釋放資源時會通知同步隊列中的等待線程。
子類通過繼承同步器并實現(xiàn)它的抽象方法getState、setState 和 compareAndSetState對同步狀態(tài)進行更改。
AQS獲取獨占鎖/釋放獨占鎖原理:
獲取:(acquire)
調用 tryAcquire 方法安全地獲取線程同步狀態(tài),獲取失敗的線程會被構造同步節(jié)點并通過 addWaiter 方法加入到同步隊列的尾部,在隊列中自旋。 調用 acquireQueued 方法使得該節(jié)點以死循環(huán)的方式獲取同步狀態(tài),如果獲取不到則阻塞。
釋放:(release)
調用 tryRelease 方法釋放同步狀態(tài) 調用 unparkSuccessor 方法喚醒頭節(jié)點的后繼節(jié)點,使后繼節(jié)點重新嘗試獲取同步狀態(tài)。
AQS獲取共享鎖/釋放共享鎖原理
獲取鎖(acquireShared)
調用 tryAcquireShared 方法嘗試獲取同步狀態(tài),返回值不小于 0 表示能獲取同步狀態(tài)。 釋放(releaseShared),并喚醒后續(xù)處于等待狀態(tài)的節(jié)點。
我是二哥呀,希望能幫助大家闖面成功,秋招成功上岸。點贊 + 在看只要來一個,我這張帥臉就泛起了笑容。
把我的座右銘送給大家吧:沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟。
推薦閱讀:
