全面總結(jié)Android面試知識(shí)要點(diǎn):Java核心基礎(chǔ)相關(guān)(三)

請(qǐng)點(diǎn)贊,你的點(diǎn)贊對(duì)我意義重大,滿足下我的虛榮心。
??常在河邊走,哪有不濕鞋?;蛟S面試過(guò)程中你遇到的問題就在這呢?
??關(guān)注個(gè)人簡(jiǎn)介,面試不迷路~

一、假如只有一個(gè)cpu,單核,多線程還有用嗎 ?

這道題想考察什么?

是否了解并發(fā)相關(guān)的理論知識(shí)

考察的知識(shí)點(diǎn)

  1. cpu多線程的基本概念
  2. 操作系統(tǒng)的調(diào)度任務(wù)機(jī)制
  3. CPU密集型和IO密集型理論

考生應(yīng)該如何回答

CPU的執(zhí)行速度要遠(yuǎn)大于IO的過(guò)程,因此在大多數(shù)情況下增加一些復(fù)雜的CPU計(jì)算都比增加一次IO要快。單核CPU可以通過(guò)給每個(gè)線程分配CPU時(shí)間片(時(shí)間單元)來(lái)實(shí)現(xiàn)多線程機(jī)制。由于CPU頻率很高,故時(shí)間單元非常短。所以單核也可以實(shí)現(xiàn)多線程機(jī)制。

從用戶體現(xiàn)上說(shuō),單核多線程也能夠減少用戶響應(yīng)時(shí)間,例如web頁(yè)面,也是防止IO阻塞。處理器的數(shù)量和并不影響程序結(jié)構(gòu), 所以不管處理器的個(gè)數(shù)多少, 程序都可以通過(guò)使用多線程得到簡(jiǎn)化。


二、sychronied修飾普通方法和靜態(tài)方法的區(qū)別?什么是可見性?(小米)

這道題想考察什么?

是否了解Java并發(fā)編程的相關(guān)知識(shí)

考察的知識(shí)點(diǎn)

  1. sychronied的原理
  2. 并發(fā)的特性

考生應(yīng)該如何回答

sychronied是Java中并發(fā)編程的重要關(guān)鍵字之一。在并發(fā)編程中synchronized一直是解決線程安全問題,它可以保證原子性,可見性,以及有序性。

  • 原子性:原子是構(gòu)成物質(zhì)的基本單位,所以原子的意思代表著—“不可分”。由不可分可知,具有原子性的操作也就是拒絕線程調(diào)度器中斷。
  • 可見性:一個(gè)線程對(duì)共享變量的修改,另一個(gè)線程能夠立刻看到,稱為可見性。
  • 有序性:程序按照代碼的先后順序執(zhí)行。編譯器為了優(yōu)化性能,有時(shí)會(huì)改變程序中語(yǔ)句的順序,但是不會(huì)影響最終的結(jié)果。有序性經(jīng)典的例子就是利用DCL雙重檢查創(chuàng)建單例對(duì)象。

synchronized可以修飾方法,也能夠使用synchronized(obj){}定義同步代碼塊。

  • 修飾方法:

    1. 實(shí)例方法,作用于當(dāng)前實(shí)例加鎖,進(jìn)入方法前需要獲取當(dāng)前實(shí)例的鎖;
    2. 靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入方法前需要獲取當(dāng)前類對(duì)象的鎖;
  • 修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入代碼塊前要獲得給定對(duì)象的鎖。

使用sychronied修飾普通方法和靜態(tài)方法,其實(shí)也等價(jià)于synchronized(this){}synchronized(class){}。


三、Synchronized在JDK1.6之后做了哪些優(yōu)化 (京東)

這道題想考察什么?

對(duì)并發(fā)編程的掌握

考察的知識(shí)點(diǎn)

并發(fā)與synchronized原理

考生如何回答

synchronized是Java中非常重要的一個(gè)關(guān)鍵字,對(duì)于Android開發(fā)同學(xué)來(lái)說(shuō),考慮到多線程的情況,一般都直接使用到synchronized關(guān)鍵字對(duì)方法或者對(duì)象上鎖。但是問題是為什么加上synchronized關(guān)鍵字就能實(shí)現(xiàn)鎖,它的原理是怎么回事呢?

字節(jié)碼

如果我們使用javap -v xxx.class 反編譯這樣一個(gè)class文件

public static void main(String[] args) {
        synchronized (InjectTest.class) {
            System.out.println("hello!");
        }
}

此時(shí)我們獲得到結(jié)果為:

可以看到j(luò)avap的輸出信息中存在:monitorenter與monitorexit指令。這就代表了同步代碼塊的入口與出口。

這里的monitor是:對(duì)象監(jiān)視器。在JVM中,每個(gè)對(duì)象都會(huì)和一個(gè)對(duì)象監(jiān)視器(monitor)相關(guān)聯(lián)。monitorenter指令插入到同步代碼塊開始的位置、monitorexit指令插入到同步代碼塊結(jié)束位置,jvm需要保證每個(gè)monitorenter都有一個(gè)monitorexit對(duì)應(yīng)。這兩個(gè)指令,本質(zhì)上都是對(duì) 對(duì)象監(jiān)視器(monitor)進(jìn)行獲取,這個(gè)過(guò)程是排他的,也就是說(shuō)同一時(shí)刻只能有一個(gè)線程獲取到由synchronized所保護(hù)對(duì)象的監(jiān)視器。線程執(zhí)行到monitorenter指令時(shí),會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor所有權(quán),也就是嘗試獲取對(duì)象的鎖;而執(zhí)行monitorexit,就是釋放monitor的所有權(quán)。 如果其他線程已經(jīng)占用了monitor,則當(dāng)前線程進(jìn)入阻塞狀態(tài)。

當(dāng)然這是jdk1.6之前的行為,而jdk1.6以后為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗,對(duì)synchronized鎖進(jìn)行了優(yōu)化,包含偏向鎖、輕量級(jí)鎖、重量級(jí)鎖;

關(guān)于鎖類型與相關(guān)信息的信息都是存放在鎖對(duì)象的對(duì)象頭中 ,在了解偏向鎖、輕量級(jí)鎖、重量級(jí)鎖之前,我們必須先認(rèn)識(shí)一下什么是對(duì)象頭!

Java對(duì)象頭

對(duì)象在虛擬機(jī)內(nèi)存中的布局分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充;Java對(duì)象頭是實(shí)現(xiàn)synchronized的鎖對(duì)象的基礎(chǔ),一般而言,synchronized使用的鎖對(duì)象是存儲(chǔ)在Java對(duì)象頭里。

對(duì)象頭由:存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù)的Mark Word 32位系統(tǒng)中4 + 指向類的指針 kClass pointer ,如果是數(shù)組對(duì)象還會(huì)有數(shù)組長(zhǎng)度 Array Length。

其中mark word中就存儲(chǔ)了鎖狀態(tài):

image.png

在無(wú)鎖狀態(tài)下,mark word中的數(shù)據(jù)為:

image.png

包含對(duì)象hashcode,gc年齡,是否偏向鎖與鎖標(biāo)志信息。

偏向鎖

而偏向鎖下,數(shù)據(jù)則為:

image.png

擁有鎖的線程ID, epoch 大家可以先理解為校驗(yàn)位,同時(shí) 是否偏向鎖標(biāo)記由相對(duì)無(wú)鎖狀態(tài)下的0變?yōu)?。

首先之所以會(huì)引入偏向鎖是因?yàn)椋捍蠖鄶?shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖,減少不必要的操作。

在程序進(jìn)入同步代碼塊時(shí),會(huì)訪問Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1,鎖標(biāo)志位是否為01,若為偏向鎖狀態(tài),則查看偏向鎖狀態(tài)下線程ID是否指向當(dāng)前線程。如果是則直接執(zhí)行同步代碼。但是mark word中記錄的線程ID如果不是當(dāng)前線程,則通過(guò)CAS比較與交換嘗試修改對(duì)象頭獲得鎖。CAS操作成功則可以直接執(zhí)行同步代碼,否則表示有其他線程競(jìng)爭(zhēng),此時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖 ,然后被阻塞的線程繼續(xù)往下執(zhí)行同步代碼。

輕量級(jí)鎖
image.png

輕量級(jí)鎖狀態(tài)下,代碼進(jìn)入同步塊時(shí),如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài), 虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝 ,接著虛擬機(jī)將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針 。這個(gè)操作如果成功則代表獲取到了鎖,但是如果失敗,則會(huì)檢查對(duì)象Mark Word是不是指向當(dāng)前線程棧幀中的鎖記錄,如果是,則說(shuō)明本身當(dāng)前線程就擁有此對(duì)象的鎖,就可以直接執(zhí)行同步代碼。否則說(shuō)明鎖對(duì)象被其他線程獲取,當(dāng)前線程是競(jìng)爭(zhēng)者,那么當(dāng)前線程會(huì)自旋等待鎖,也就是不斷重試,當(dāng)重試一定次數(shù)后,總不能一直重試下去吧,太耗CPU了。所以這時(shí)候就要升級(jí)為重量級(jí)鎖。

重量級(jí)鎖

重量級(jí)鎖就是通過(guò)對(duì)象監(jiān)視器(monitor)實(shí)現(xiàn),其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實(shí)現(xiàn),操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,切換成本非常高。主要是,當(dāng)系統(tǒng)檢查到鎖是重量級(jí)鎖之后,會(huì)把等待想要獲得鎖的線程進(jìn)行阻塞,被阻塞的線程不會(huì)消耗cup。但是阻塞或者喚醒一個(gè)線程時(shí),都需要操作系統(tǒng)來(lái)幫忙,這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),而轉(zhuǎn)換狀態(tài)是需要消耗很多時(shí)間的,有可能比用戶執(zhí)行代碼的時(shí)間還要長(zhǎng)。

四、CAS無(wú)鎖編程的原理(字節(jié)跳動(dòng))

這道題想考察什么?

并發(fā)相關(guān)問題,原子操作

考察的知識(shí)點(diǎn)

Java并發(fā)編程,樂觀鎖機(jī)制

考生如何回答

Jdk中java.util.concurrent.atomic包下的類都是采用CAS來(lái)實(shí)現(xiàn)的。

CAS原理分析

CAS(比較與交換,Compare and swap) 是一種有名的無(wú)鎖算法。無(wú)鎖編程,即不使用鎖的情況下實(shí)現(xiàn)多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實(shí)現(xiàn)變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。實(shí)現(xiàn)非阻塞同步的方案稱為“無(wú)鎖編程算法”( Non-blocking algorithm)。

CAS機(jī)制當(dāng)中使用了3個(gè)基本操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),要修改的新值B。更新一個(gè)變量的時(shí)候,只有當(dāng)變量的預(yù)期值A(chǔ)和內(nèi)存地址V當(dāng)中的實(shí)際值相同時(shí),才會(huì)將內(nèi)存地址V對(duì)應(yīng)的值修改為B。

1.在內(nèi)存地址V當(dāng)中,存儲(chǔ)著值為10的變量。

image.png

2.此時(shí)線程1想要把變量的值增加1。對(duì)線程1來(lái)說(shuō),舊的預(yù)期值A(chǔ)=10,要修改的新值B=11。

image.png

3.在線程1要提交更新之前,另一個(gè)線程2搶先一步,把內(nèi)存地址V中的變量值率先更新成了11。

image.png

4.線程1開始提交更新,首先進(jìn)行A和地址V的實(shí)際值比較(Compare) ,發(fā)現(xiàn)A不等于V的實(shí)際值,提交失敗。

image.png

5.線程1重新獲取內(nèi)存地址V的當(dāng)前值,并重新計(jì)算想要修改的新值。此時(shí)對(duì)線程1來(lái)說(shuō),A=11,B=12。這個(gè)重新嘗試的過(guò)程被稱為自旋

image.png

6.這一次比較幸運(yùn),沒有其他線程改變地址V的值。線程1進(jìn)行Compare,發(fā)現(xiàn)A和地址V的實(shí)際值是相等的。

image.png

7.線程1進(jìn)行SWAP,把地址V的值替換為B,也就是12。

image.png

從思想上來(lái)說(shuō),Synchronized屬于悲觀鎖,悲觀地認(rèn)為程序中的并發(fā)情況嚴(yán)重,所以嚴(yán)防死守。CAS屬于樂觀鎖,樂觀地認(rèn)為程序中的并發(fā)情況不那么嚴(yán)重,所以讓線程不斷去嘗試更新。

CAS的缺點(diǎn)

ABA 問題

由于 CAS 設(shè)計(jì)機(jī)制就是獲取某兩個(gè)時(shí)刻(初始預(yù)期值和當(dāng)前內(nèi)存值)變量值,并進(jìn)行比較更新,所以說(shuō)如果在獲取初始預(yù)期值和當(dāng)前內(nèi)存值這段時(shí)間間隔內(nèi),變量值由 A 變?yōu)?B 再變?yōu)?A,那么對(duì)于 CAS 來(lái)說(shuō)是不可感知的,但實(shí)際上變量已經(jīng)發(fā)生了變化;解決辦法是在每次獲取時(shí)加版本號(hào),并且每次更新對(duì)版本號(hào) +1,這樣當(dāng)發(fā)生 ABA 問題時(shí)通過(guò)版本號(hào)可以得知變量被改動(dòng)過(guò)。JDK 1.5 以后的 AtomicStampedReference 類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

循環(huán)時(shí)間長(zhǎng)開銷大

所謂循環(huán)時(shí)間長(zhǎng)開銷大問題就是當(dāng) CAS 判定變量被修改了以后則放棄本次修改,但往往為了保證數(shù)據(jù)正確性該計(jì)算會(huì)以循環(huán)的方式再次發(fā)起 CAS,如果多次 CAS 判定失敗,則會(huì)產(chǎn)生大量的時(shí)間消耗和性能浪費(fèi);如果JVM能支持處理器提供的pause指令那么效率會(huì)有一定的提升,pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會(huì)消耗過(guò)多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率。

只能保證一個(gè)共享變量的原子操作
  1. CAS 只對(duì)單個(gè)共享變量有效,當(dāng)操作涉及跨多個(gè)共享變量時(shí) CAS 無(wú)效;從 JDK 1.5開始提供了 AtomicReference 類來(lái)保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行 CAS 操作
  2. Unsafe是CAS的核心類,Java無(wú)法直接訪問底層操作系統(tǒng),而是通過(guò)本地(native)方法來(lái)訪問。不過(guò)盡管如此,JVM還是開了一個(gè)后門,JDK中有一個(gè)類Unsafe,它提供了硬件級(jí)別的原子操作。
  3. valueOffset表示的是變量值在內(nèi)存中的偏移地址,因?yàn)閁nsafe就是根據(jù)內(nèi)存偏移地址獲取數(shù)據(jù)的原值的。
  4. value是用volatile修飾的,保證了多線程之間看到的value值是同一份。

今天的面試分享到此結(jié)束拉~下期在見

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容