掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號「后端技術(shù)小牛說」,作者后端技術(shù)小牛說。轉(zhuǎn)載本文請聯(lián)系后端技術(shù)小牛說公眾號。

最近有收到讀者的一些反饋,感謝了我開發(fā)了網(wǎng)站interviewtop.top,對他們面試產(chǎn)生了巨大幫助,更有讀者給我發(fā)了小紅包贊助我。當然啦,我也知道大家還是以學生群體為主,紅包和贊助免了哈!
我之前還是學生身份,分別租了阿里云騰訊云等等云服務(wù)器的9.9元學生套餐,利用各個廠的學生福利云服務(wù)器負載均衡做服務(wù)器后端,這幾個月,由于已經(jīng)成為社會人了,當然沒這點福利了(9.9同等配置社會人現(xiàn)在得近200,所以還在上學的同學有需求這個羊毛可以薅一把)。而且隨著大家使用頻率的增高,配置要求當然也不一定打的住了。
除此之外,interviewtop網(wǎng)站也需要大家的幫助和支持!如果大家有面經(jīng),希望大家能貢獻一下:
在全部和對應(yīng)題庫下點擊搜索
搜索自己的面試題
點擊這題我面試見過,貢獻一下自己的面試經(jīng)歷
如果有些題題庫沒收錄,歡迎大家添加小牛微信,小牛后臺更新上!
java內(nèi)存模型定義了程序中各種變量的訪問規(guī)則。其規(guī)定所有變量都存儲在主內(nèi)存,線程均有自己的工作內(nèi)存。工作內(nèi)存中保存被該線程使用的變量的主內(nèi)存副本,線程對變量的所有操作都必須在工作空間進行,不能直接讀寫主內(nèi)存數(shù)據(jù)。操作完成后,線程的工作內(nèi)存通過緩存一致性協(xié)議將操作完的數(shù)據(jù)刷回主存。
編譯器等會對原始的程序進行指令重排序和優(yōu)化。但不管怎么重排序,其結(jié)果和用戶原始程序輸出預(yù)定結(jié)果一致。
程序次序規(guī)則:一個線程內(nèi)寫在前面的操作先行發(fā)生于后面的。
鎖定規(guī)則:unlock 操作先行發(fā)生于后面對同一個鎖的 lock 操作。
volatile 規(guī)則:對 volatile 變量的寫操作先行發(fā)生于后面的讀操作。
線程啟動規(guī)則:線程的 start 方法先行發(fā)生于線程的每個動作。
線程中斷規(guī)則:對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
線程終止規(guī)則:線程中所有操作先行發(fā)生于對線程的終止檢測。
對象終結(jié)規(guī)則:對象的初始化先行發(fā)生于 finalize 方法。
傳遞性規(guī)則:如果操作 A 先行發(fā)生于操作 B,操作 B 先行發(fā)生于操作 C,那么操作 A 先行發(fā)生于操作 C
as-if-serial 保證單線程程序的執(zhí)行結(jié)果不變,happens-before 保證正確同步的多線程程序的執(zhí)行結(jié)果不變。
一個操作或者多個操作,要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行,這就是原子性操作。
可見性指當一個線程修改了共享變量時,其他線程能夠立即得知修改。volatile,synchronized,final都能保證可見性。
即雖然多線程存在并發(fā)和指令優(yōu)化等操作,在本線程內(nèi)觀察該線程的所有執(zhí)行操作是有序的。
保證變量對所有線程的可見性。當一條線程修改了變量值,新值對于其他線程來說是立即可以得知的。
禁止指令重排序優(yōu)化。使用 volatile 變量進行寫操作,匯編指令帶有 lock 前綴,相當于一個內(nèi)存屏障,編譯器不會將后面的指令重排到內(nèi)存屏障之前。
線程狀態(tài)有New, RUNNABLE, BLOCK, WAITING, TIMED_WAITING, THERMINATED NEW:新建狀態(tài),線程被創(chuàng)建且未啟動,此時還未調(diào)用 start 方法。
RUNNABLE: 運行狀態(tài)。其表示線程正在JVM中執(zhí)行,但是這個執(zhí)行,不一定真的在跑,也可能在排隊等CPU。
BLOCKED:阻塞狀態(tài)。線程等待獲取鎖,鎖還沒獲得。
WAITING: 等待狀態(tài)。線程內(nèi)run方法運行完語句Object.wait()/Thread.join()進入該狀態(tài)。
TIMED_WAITING:限期等待。在一定時間之后跳出狀態(tài)。調(diào)用Thread.sleep(long) Object.wait(long) Thread.join(long)進入狀態(tài)。其中這些參數(shù)代表等待的時間。
TERMINATED:結(jié)束狀態(tài)。線程調(diào)用完run方法進入該狀態(tài)。
沒有線程池的情況下,多次創(chuàng)建,銷毀線程開銷比較大。如果在開辟的線程執(zhí)行完當前任務(wù)后執(zhí)行接下來任務(wù),復(fù)用已創(chuàng)建的線程,降低開銷、控制最大并發(fā)數(shù)。
線程池創(chuàng)建線程時,會將線程封裝成工作線程 Worker,Worker 在執(zhí)行完任務(wù)后還會循環(huán)獲取工作隊列中的任務(wù)來執(zhí)行。
將任務(wù)派發(fā)給線程池時,會出現(xiàn)以下幾種情況
核心線程池未滿,創(chuàng)建一個新的線程執(zhí)行任務(wù)。
如果核心線程池已滿,工作隊列未滿,將線程存儲在工作隊列。
如果工作隊列已滿,線程數(shù)小于最大線程數(shù)就創(chuàng)建一個新線程處理任務(wù)。
如果超過大小線程數(shù),按照拒絕策略來處理任務(wù)。
newFixedThreadPool,創(chuàng)建固定大小的線程池。
newSingleThreadExecutor,使用單線程線程池。
newCachedThreadPool,maximumPoolSize 設(shè)置為 Integer 最大值,工作完成后會回收工作線程
newScheduledThreadPool:支持定期及周期性任務(wù)執(zhí)行,不回收工作線程。
newWorkStealingPool:一個擁有多個任務(wù)隊列的線程池。
Executor框架目的是將任務(wù)提交和任務(wù)如何運行分離開來的機制。用戶不再需要從代碼層考慮設(shè)計任務(wù)的提交運行,只需要調(diào)用Executor框架實現(xiàn)類的Execute方法就可以提交任務(wù)。產(chǎn)生線程池的函數(shù)ThreadPoolExecutor也是Executor的具體實現(xiàn)類。
阻塞隊列是生產(chǎn)者消費者的實現(xiàn)具體組件之一。當阻塞隊列為空時,從隊列中獲取元素的操作將會被阻塞,當阻塞隊列滿了,往隊列添加元素的操作將會被阻塞。具體實現(xiàn)有:
ThreadLocal 是線程共享變量。ThreadLoacl 有一個靜態(tài)內(nèi)部類 ThreadLocalMap,其 Key 是 ThreadLocal 對象,值是 Entry 對象,ThreadLocalMap是每個線程私有的。
存在的問題
對于 Java 語言,沒有直接的指針組件,一般也不能使用偏移量對某塊內(nèi)存進行操作。這些操作相對來講是安全(safe)的。
Java 有個類叫 Unsafe 類,這個類類使 Java 擁有了像 C 語言的指針一樣操作內(nèi)存空間的能力,同時也帶來了指針的問題。這個類可以說是 Java 并發(fā)開發(fā)的基礎(chǔ)。
對于樂觀鎖,開發(fā)者認為數(shù)據(jù)發(fā)送時發(fā)生并發(fā)沖突的概率不大,所以讀操作前不上鎖。
到了寫操作時才會進行判斷,數(shù)據(jù)在此期間是否被其他線程修改。如果發(fā)生修改,那就返回寫入失敗;如果沒有被修改,那就執(zhí)行修改操作,返回修改成功。
樂觀鎖一般都采用 Compare And Swap(CAS)算法進行實現(xiàn)。顧名思義,該算法涉及到了兩個操作,比較(Compare)和交換(Swap)。
CAS 算法的思路如下:
CAS 算法是基于值來做比較的,如果當前有兩個線程,一個線程將變量值從 A 改為 B ,再由 B 改回為 A ,當前線程開始執(zhí)行 CAS 算法時,就很容易認為值沒有變化,誤認為讀取數(shù)據(jù)到執(zhí)行 CAS 算法的期間,沒有線程修改過數(shù)據(jù)。
juc 包提供了一個 AtomicStampedReference,即在原始的版本下加入版本號戳,解決 ABA 問題。
在很多時候,我們需要的僅僅是一個簡單的、高效的、線程安全的++或者--方案,使用synchronized關(guān)鍵字和lock固然可以實現(xiàn),但代價比較大,此時用原子類更加方便?;緮?shù)據(jù)類型的原子類有:
Atomic數(shù)組類型有:
Atomic引用類型有
FieldUpdater類型:
以AtomicIntger 為例:方法getAndIncrement:以原子方式將當前的值加1,具體實現(xiàn)為:
countDownLatch這個類使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。是通過一個計數(shù)器來實現(xiàn)的,計數(shù)器的初始值是線程的數(shù)量。每當一個線程執(zhí)行完畢后,調(diào)用countDown方法,計數(shù)器的值就減1,當計數(shù)器的值為0時,表示所有線程都執(zhí)行完畢,然后在等待的線程就可以恢復(fù)工作了。只能一次性使用,不能reset。
CyclicBarrier 主要功能和countDownLatch類似,也是通過一個計數(shù)器,使一個線程等待其他線程各自執(zhí)行完畢后再執(zhí)行。但是其可以重復(fù)使用(reset)。
Semaphore即信號量。Semaphore 的構(gòu)造方法參數(shù)接收一個 int 值,設(shè)置一個計數(shù)器,表示可用的許可數(shù)量即最大并發(fā)數(shù)。使用 acquire 方法獲得一個許可證,計數(shù)器減一,使用 release 方法歸還許可,計數(shù)器加一。如果此時計數(shù)器值為0,線程進入休眠。
Exchanger類可用于兩個線程之間交換信息??珊唵蔚貙xchanger對象理解為一個包含兩個格子的容器,通過exchanger方法可以向兩個格子中填充信息。線程通過exchange 方法交換數(shù)據(jù),第一個線程執(zhí)行 exchange 方法后會阻塞等待第二個線程執(zhí)行該方法。當兩個線程都到達同步點時這兩個線程就可以交換數(shù)據(jù)當兩個格子中的均被填充時,該對象會自動將兩個格子的信息交換,然后返回給線程,從而實現(xiàn)兩個線程的信息交換。
JDK7采用鎖分段技術(shù)。首先將數(shù)據(jù)分成 Segment 數(shù)據(jù)段,然后給每一個數(shù)據(jù)段配一把鎖,當一個線程占用鎖訪問其中一個段的數(shù)據(jù)時,其他段的數(shù)據(jù)也能被其他線程訪問。
get 除讀到空值不需要加鎖。該方法先經(jīng)過一次再散列,再用這個散列值通過散列運算定位到 Segment,最后通過散列算法定位到元素。put 須加鎖,首先定位到 Segment,然后進行插入操作,第一步判斷是否需要對 Segment 里的 HashEntry 數(shù)組進行擴容,第二步定位添加元素的位置,然后將其放入數(shù)組。
JDK8的改進
Java 對象底層都關(guān)聯(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 鎖隨即就被釋放。
JDK 1.6 中提出了偏向鎖的概念。該鎖提出的原因是,開發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競爭,一把鎖往往是由同一個線程獲得的。偏向鎖并不會主動釋放,這樣每次偏向鎖進入的時候都會判斷該資源是否是偏向自己的,如果是偏向自己的則不需要進行額外的操作,直接可以進入同步操作。
其申請流程為:
輕量級鎖是為了在沒有競爭的前提下減少重量級鎖出現(xiàn)并導(dǎo)致的性能消耗。
其申請流程為:
即自適應(yīng)自旋、鎖消除、鎖粗化、鎖升級等策略偏。
線程獲取鎖失敗后,可以采用這樣的策略,可以不放棄 CPU ,不停的重試內(nèi)重試,這種操作也稱為自旋鎖。
自適應(yīng)自旋鎖自旋次數(shù)不再人為設(shè)定,通常由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)決定。
鎖粗化的思想就是擴大加鎖范圍,避免反復(fù)的加鎖和解鎖。
鎖消除是一種更為徹底的優(yōu)化,在編譯時,java編譯器對運行上下文進行掃描,去除不可能存在共享資源競爭的鎖。
Lock 接是 java并發(fā)包的頂層接口。
可重入鎖 ReentrantLock 是 Lock 最常見的實現(xiàn),與 synchronized 一樣可重入。ReentrantLock 在默認情況下是非公平的,可以通過構(gòu)造方法指定公平鎖。一旦使用了公平鎖,性能會下降。
AQS(AbstractQuenedSynchronizer)抽象的隊列式同步器。AQS是將每一條請求共享資源的線程封裝成一個鎖隊列的一個結(jié)點(Node),來實現(xiàn)鎖的分配。AQS是用來構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,它使用一個 volatile int state 變量作為共享資源,如果線程獲取資源失敗,則進入同步隊列等待;如果獲取成功就執(zhí)行臨界區(qū)代碼,釋放資源時會通知同步隊列中的等待線程。
子類通過繼承同步器并實現(xiàn)它的抽象方法getState、setState 和 compareAndSetState對同步狀態(tài)進行更改。
獲?。?acquire)
釋放:(release)
獲取鎖(acquireShared)
調(diào)用 tryAcquireShared 方法嘗試獲取同步狀態(tài),返回值不小于 0 表示能獲取同步狀態(tài)。
釋放(releaseShared)
釋放,并喚醒后續(xù)處于等待狀態(tài)的節(jié)點。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流