欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java經(jīng)典面試題最全匯總208道(二)

 更新時間:2023年01月17日 15:55:07   作者:哪?吒  
這篇文章主要介紹了Java經(jīng)典面試題最全匯總208道(二),本文章內(nèi)容詳細(xì),該模塊分為了六個部分,本次為第二部分,需要的朋友可以參考下<BR>

前言 

短時間提升自己最快的手段就是背面試題,最近總結(jié)了Java常用的面試題,分享給大家,希望大家都能圓夢大廠,加油,我命由我不由天。

53、concurrentHashMap和HashTable有什么區(qū)別

concurrentHashMap融合了hashmap和hashtable的優(yōu)勢,hashmap是不同步的,但是單線程情況下效率高,hashtable是同步的同步情況下保證程序執(zhí)行的正確性。

但hashtable每次同步執(zhí)行的時候都要鎖住整個結(jié)構(gòu),如下圖:

concurrentHashMap鎖的方式是細(xì)粒度的。

concurrentHashMap將hash分為16個桶(默認(rèn)值),諸如get、put、remove等常用操作只鎖住當(dāng)前需要用到的桶。

concurrentHashMap的讀取并發(fā),因為讀取的大多數(shù)時候都沒有鎖定,所以讀取操作幾乎是完全的并發(fā)操作,只是在求size時才需要鎖定整個hash。

而且在迭代時,concurrentHashMap使用了不同于傳統(tǒng)集合的快速失敗迭代器的另一種迭代方式,弱一致迭代器。

在這種方式中,當(dāng)iterator被創(chuàng)建后集合再發(fā)生改變就不會拋出ConcurrentModificationException,取而代之的是在改變時new新的數(shù)據(jù)而不是影響原來的數(shù)據(jù),iterator完成后再講頭指針替代為新的數(shù)據(jù),這樣iterator時使用的是原來的數(shù)據(jù)。

54、HasmMap和HashSet的區(qū)別

(1)先了解一下HashCode

Java中的集合有兩類,一類是List,一類是Set。

List:元素有序,可以重復(fù);

Set:元素?zé)o序,不可重復(fù);

要想保證元素的不重復(fù),拿什么來判斷呢?這就是Object.equals方法了。

如果元素有很多,增加一個元素,就要判斷n次嗎?

顯然不現(xiàn)實,于是,Java采用了哈希表的原理。

哈希算法也稱為散列算法,是將數(shù)據(jù)依特定算法直接指定到一根地址上,初學(xué)者可以簡單的理解為,HashCode方法返回的就是對象存儲的物理位置(實際上并不是)。

這樣一來,當(dāng)集合添加新的元素時,先調(diào)用這個元素的hashcode()方法,就一下子能定位到他應(yīng)該放置的物理位置上。

如果這個位置上沒有元素,他就可以直接存儲在這個位置上,不用再進行任何比較了。

如果這個位置上有元素,就調(diào)用它的equals方法與新元素進行比較,想同的話就不存了,不相同就散列其它的地址。

所以這里存在一個沖突解決的問題。

這樣一來實際上調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。

簡而言之,在集合查找時,hashcode能大大降低對象比較次數(shù),提高查找效率。

Java對象的equals方法和hashCode方法時這樣規(guī)定的:

相等的對象就必須具有相等的hashcode。

  1. 如果兩個對象的hashcode相同,他們并不一定相同。
  2. 如果兩個對象的hashcode相同,他們并不一定相同。

如果兩個Java對象A和B,A和B不相等,但是A和B的哈希碼相等,將A和B都存入HashMap時會發(fā)生哈希沖突,也就是A和B存放在HashMap內(nèi)部數(shù)組的位置索引相同

這時HashMap會在該位置建立一個鏈接表,將A和B串起來放在該位置,顯然,該情況不違反HashMap的使用規(guī)則,是允許的。

當(dāng)然,哈希沖突越少越好,盡量采用好的哈希算法避免哈希沖突。

equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。

(2)HashMap和HashSet的區(qū)別

55、請談?wù)?ReadWriteLock 和 StampedLock

ReadWriteLock包括兩種子鎖

(1)ReadWriteLock

ReadWriteLock 可以實現(xiàn)多個讀鎖同時進行,但是讀與寫和寫于寫互斥,只能有一個寫鎖線程在進行。

(2)StampedLock

StampedLock是Jdk在1.8提供的一種讀寫鎖,相比較ReentrantReadWriteLock性能更好

因為ReentrantReadWriteLock在讀寫之間是互斥的,使用的是一種悲觀策略,在讀線程特別多的情況下,會造成寫線程處于饑餓狀態(tài)

雖然可以在初始化的時候設(shè)置為true指定為公平,但是吞吐量又下去了,而StampedLock是提供了一種樂觀策略,更好的實現(xiàn)讀寫分離,并且吞吐量不會下降。

StampedLock包括三種鎖:

(1)寫鎖writeLock:

writeLock是一個獨占鎖寫鎖,當(dāng)一個線程獲得該鎖后,其他請求讀鎖或者寫鎖的線程阻塞, 獲取成功后,會返回一個stamp(憑據(jù))變量來表示該鎖的版本,在釋放鎖時調(diào)用unlockWrite方法傳遞stamp參數(shù)。

提供了非阻塞式獲取鎖tryWriteLock。

(2)悲觀讀鎖readLock:

readLock是一個共享讀鎖,在沒有線程獲取寫鎖情況下,多個線程可以獲取該鎖。

如果有寫鎖獲取,那么其他線程請求讀鎖會被阻塞。

悲觀讀鎖會認(rèn)為其他線程可能要對自己操作的數(shù)據(jù)進行修改,所以需要先對數(shù)據(jù)進行加鎖,這是在讀少寫多的情況下考慮的。

請求該鎖成功后會返回一個stamp值,在釋放鎖時調(diào)用unlockRead方法傳遞stamp參數(shù)。

提供了非阻塞式獲取鎖方法tryWriteLock。

(3)樂觀讀鎖tryOptimisticRead:

tryOptimisticRead相對比悲觀讀鎖,在操作數(shù)據(jù)前并沒有通過CAS設(shè)置鎖的狀態(tài),如果沒有線程獲取寫鎖,則返回一個非0的stamp變量,獲取該stamp后在操作數(shù)據(jù)前還需要調(diào)用validate方法來判斷期間是否有線程獲取了寫鎖

如果是返回值為0則有線程獲取寫鎖,如果不是0則可以使用stamp變量的鎖來操作數(shù)據(jù)。

由于tryOptimisticRead并沒有修改鎖狀態(tài),所以不需要釋放鎖。

這是讀多寫少的情況下考慮的,不涉及CAS操作,所以效率較高,在保證數(shù)據(jù)一致性上需要復(fù)制一份要操作的變量到方法棧中,并且在操作數(shù)據(jù)時可能其他寫線程已經(jīng)修改了數(shù)據(jù)

而我們操作的是方法棧里面的數(shù)據(jù),也就是一個快照,所以最多返回的不是最新的數(shù)據(jù),但是一致性得到了保證。

56、線程的run()和start()有什么區(qū)別?

每個線程都是通過某個特定Thread對象所對應(yīng)的方法run()來完成其操作的,run()方法稱為線程體。

通過調(diào)用Thread類的start()方法來啟動一個線程。

start() 方法用于啟動線程,run() 方法用于執(zhí)行線程的運行時代碼。

run() 可以重復(fù)調(diào)用,而 start() 只能調(diào)用一次。

start()方法來啟動一個線程,真正實現(xiàn)了多線程運行。

調(diào)用start()方法無需等待run方法體代碼執(zhí)行完畢,可以直接繼續(xù)執(zhí)行其他的代碼; 此時線程是處于就緒狀態(tài),并沒有運行。

然后通過此Thread類調(diào)用方法run()來完成其運行狀態(tài), run()方法運行結(jié)束, 此線程終止。然后CPU再調(diào)度其它線程。

run()方法是在本線程里的,只是線程里的一個函數(shù),而不是多線程的。

如果直接調(diào)用run(),其實就相當(dāng)于是調(diào)用了一個普通函數(shù)而已,直接待用run()方法必須等待run()方法執(zhí)行完畢才能執(zhí)行下面的代碼

所以執(zhí)行路徑還是只有一條,根本就沒有線程的特征,所以在多線程執(zhí)行時要使用start()方法而不是run()方法。

57、為什么我們調(diào)用 start() 方法時會執(zhí)行 run() 方法,為什么我們不能直接調(diào)用 run() 方法?

這是另一個非常經(jīng)典的 java 多線程面試問題,而且在面試中會經(jīng)常被問到。很簡單,但是很多人都會答不上來!

new 一個 Thread,線程進入了新建狀態(tài)。

調(diào)用 start() 方法,會啟動一個線程并使線程進入了就緒狀態(tài),當(dāng)分配到時間片后就可以開始運行了。

start() 會執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作。

而直接執(zhí)行 run() 方法,會把 run 方法當(dāng)成一個 main 線程下的普通方法去執(zhí)行,并不會在某個線程中執(zhí)行它,所以這并不是多線程工作。

總結(jié): 調(diào)用 start 方法方可啟動線程并使線程進入就緒狀態(tài),而 run 方法只是 thread 的一個普通方法調(diào)用,還是在主線程里執(zhí)行。

58、Synchronized 用過嗎,其原理是什么?

(1)可重入性

synchronized的鎖對象中有一個計數(shù)器(recursions變量)會記錄線程獲得幾次鎖;

  1. 可重入的好處;
  2. 可以避免死鎖;
  3. 可以讓我們更好的封裝代碼;

synchronized是可重入鎖,每部鎖對象會有一個計數(shù)器記錄線程獲取幾次鎖,在執(zhí)行完同步代碼塊時,計數(shù)器的數(shù)量會-1,直到計數(shù)器的數(shù)量為0,就釋放這個鎖。

(2)不可中斷性

  1. 一個線程獲得鎖后,另一個線程想要獲得鎖,必須處于阻塞或等待狀態(tài),如果第一個線程不釋放鎖,第二個線程會一直阻塞或等待,不可被中斷;
  2. synchronized 屬于不可被中斷;
  3. Lock lock方法是不可中斷的;
  4. Lock tryLock方法是可中斷的;

59、JVM 對 Java 的原生鎖做了哪些優(yōu)化?

(1)自旋鎖

在線程進行阻塞的時候,先讓線程自旋等待一段時間,可能這段時間其它線程已經(jīng)解鎖,這時就無需讓線程再進行阻塞操作了。

自旋默認(rèn)次數(shù)是10次。

(2)自適應(yīng)自旋鎖

自旋鎖的升級,自旋的次數(shù)不再固定,由前一次自旋次數(shù)和鎖的擁有者的狀態(tài)決定。

(3)鎖消除

在動態(tài)編譯同步代碼塊的時候,JIT編譯器借助逃逸分析技術(shù)來判斷鎖對象是否只被一個線程訪問,而沒有其他線程,這時就可以取消鎖了。

(4)鎖粗化

當(dāng)JIT編譯器發(fā)現(xiàn)一系列的操作都對同一個對象反復(fù)加鎖解鎖,甚至加鎖操作出現(xiàn)在循環(huán)中,此時會將加鎖同步的范圍粗化到整個操作系列的外部。

鎖粒度:不要鎖住一些無關(guān)的代碼。

鎖粗化:可以一次性執(zhí)行完的不要多次加鎖執(zhí)行。

60、為什么 wait(), notify()和 notifyAll()必須在同步方法或者同步塊中被調(diào)用?

Java中,任何對象都可以作為鎖,并且 wait(),notify()等方法用于等待對象的鎖或者喚醒線程,在 Java 的線程中并沒有可供任何對象使用的鎖,所以任意對象調(diào)用方法一定定義在Object類中。

wait(), notify()和 notifyAll()這些方法在同步代碼塊中調(diào)用

有的人會說,既然是線程放棄對象鎖,那也可以把wait()定義在Thread類里面啊,新定義的線程繼承于Thread類,也不需要重新定義wait()方法的實現(xiàn)。

然而,這樣做有一個非常大的問題,一個線程完全可以持有很多鎖,你一個線程放棄鎖的時候,到底要放棄哪個鎖?

當(dāng)然了,這種設(shè)計并不是不能實現(xiàn),只是管理起來更加復(fù)雜。

綜上所述,wait()、notify()和notifyAll()方法要定義在Object類中。

61、Java 如何實現(xiàn)多線程之間的通訊和協(xié)作?

可以通過中斷 和 共享變量的方式實現(xiàn)線程間的通訊和協(xié)作

比如說最經(jīng)典的生產(chǎn)者-消費者模型:當(dāng)隊列滿時,生產(chǎn)者需要等待隊列有空間才能繼續(xù)往里面放入商品,而在等待的期間內(nèi),生產(chǎn)者必須釋放對臨界資源(即隊列)的占用權(quán)。

因為生產(chǎn)者如果不釋放對臨界資源的占用權(quán),那么消費者就無法消費隊列中的商品,就不會讓隊列有空間,那么生產(chǎn)者就會一直無限等待下去。

因此,一般情況下,當(dāng)隊列滿時,會讓生產(chǎn)者交出對臨界資源的占用權(quán),并進入掛起狀態(tài)。

然后等待消費者消費了商品,然后消費者通知生產(chǎn)者隊列有空間了。

同樣地,當(dāng)隊列空時,消費者也必須等待,等待生產(chǎn)者通知它隊列中有商品了。

這種互相通信的過程就是線程間的協(xié)作。

Java中線程通信協(xié)作的最常見的兩種方式:

1、syncrhoized加鎖的線程的Object類的wait()/notify()/notifyAll()

2、ReentrantLock類加鎖的線程的Condition類的await()/signal()/signalAll()

線程間直接的數(shù)據(jù)交換:

通過管道進行線程間通信:

1)字節(jié)流;

2)字符流

62、Thread 類中的 yield 方法有什么作用?

yield()應(yīng)該做的是讓當(dāng)前運行線程回到可運行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運行機會。

因此,使用yield()的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。

但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調(diào)度程序再次選中。

結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運行狀態(tài)轉(zhuǎn)到可運行狀態(tài),但有可能沒有效果。

63、為什么說 Synchronized 是非公平鎖?

當(dāng)鎖被釋放后,任何一個線程都有機會競爭得到鎖,這樣做的目的是提高效率,但缺點是可能產(chǎn)生線程饑餓現(xiàn)象。

64、請談?wù)?volatile 有什么特點,為什么它能保證變量對所有線程的可見性?

volatile只能作用于變量,保證了操作可見性和有序性,不保證原子性。

在Java的內(nèi)存模型中分為主內(nèi)存和工作內(nèi)存,Java內(nèi)存模型規(guī)定所有的變量存儲在主內(nèi)存中,每條線程都有自己的工作內(nèi)存。

主內(nèi)存和工作內(nèi)存之間的交互分為8個原子操作:

  1. lock
  2. unlock
  3. read
  4. load
  5. assign
  6. use
  7. store
  8. write

volatile修飾的變量,只有對volatile進行assign操作,才可以load,只有l(wèi)oad才可以use,,這樣就保證了在工作內(nèi)存操作volatile變量,都會同步到主內(nèi)存中。

65、為什么說 Synchronized 是一個悲觀鎖?樂觀鎖的實現(xiàn)原理又是什么?什么是 CAS,它有什么特性?

Synchronized的并發(fā)策略是悲觀的,不管是否產(chǎn)生競爭,任何數(shù)據(jù)的操作都必須加鎖。

樂觀鎖的核心是CAS,CAS包括內(nèi)存值、預(yù)期值、新值,只有當(dāng)內(nèi)存值等于預(yù)期值時,才會將內(nèi)存值修改為新值。

66、樂觀鎖一定就是好的嗎?

樂觀鎖認(rèn)為對一個對象的操作不會引發(fā)沖突,所以每次操作都不進行加鎖,只是在最后提交更改時驗證是否發(fā)生沖突,如果沖突則再試一遍,直至成功為止,這個嘗試的過程稱為自旋。

樂觀鎖沒有加鎖,但樂觀鎖引入了ABA問題,此時一般采用版本號進行控制;

也可能產(chǎn)生自旋次數(shù)過多問題,此時并不能提高效率,反而不如直接加鎖的效率高;

只能保證一個對象的原子性,可以封裝成對象,再進行CAS操作;

67、請盡可能詳盡地對比下 Synchronized 和 ReentrantLock 的異同。

(1)相似點

它們都是阻塞式的同步,也就是說一個線程獲得了對象鎖,進入代碼塊,其它訪問該同步塊的線程都必須阻塞在同步代碼塊外面等待,而進行線程阻塞和喚醒的代碼是比較高的。

(2)功能區(qū)別

Synchronized是java語言的關(guān)鍵字,是原生語法層面的互斥,需要JVM實現(xiàn);ReentrantLock 是JDK1.5之后提供的API層面的互斥鎖,需要lock和unlock()方法配合try/finally代碼塊來完成。

Synchronized使用較ReentrantLock 便利一些;

鎖的細(xì)粒度和靈活性:ReentrantLock強于Synchronized;

(3)性能區(qū)別

Synchronized引入偏向鎖,自旋鎖之后,兩者的性能差不多,在這種情況下,官方建議使用Synchronized。

① Synchronized

Synchronized會在同步塊的前后分別形成monitorenter和monitorexit兩個字節(jié)碼指令。

在執(zhí)行monitorenter指令時,首先要嘗試獲取對象鎖。如果這個對象沒被鎖定,或者當(dāng)前線程已經(jīng)擁有了那個對象鎖,把鎖的計數(shù)器+1,相應(yīng)的執(zhí)行monitorexit時,計數(shù)器-1,當(dāng)計數(shù)器為0時,鎖就會被釋放。

如果獲取鎖失敗,當(dāng)前線程就要阻塞,知道對象鎖被另一個線程釋放為止。

② ReentrantLock

ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有如下三項:

等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程可以選擇放棄等待,這相當(dāng)于Synchronized避免出現(xiàn)死鎖的情況。

通過lock.lockInterruptibly()來實現(xiàn)這一機制;
公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序獲得鎖,Synchronized鎖是非公平鎖;

ReentrantLock默認(rèn)也是非公平鎖,可以通過參數(shù)true設(shè)為公平鎖,但公平鎖表現(xiàn)的性能不是很好;
鎖綁定多個條件,一個ReentrantLock對象可以同時綁定多個對象。

ReentrantLock提供了一個Condition(條件)類,用來實現(xiàn)分組喚醒需要喚醒的線程們,而不是像Synchronized要么隨機喚醒一個線程,要么喚醒全部線程。

68、ReentrantLock 是如何實現(xiàn)可重入性的?

(1)什么是可重入性

一個線程持有鎖時,當(dāng)其他線程嘗試獲取該鎖時,會被阻塞;而這個線程嘗試獲取自己持有鎖時,如果成功說明該鎖是可重入的,反之則不可重入。

(2)synchronized是如何實現(xiàn)可重入性

synchronized關(guān)鍵字經(jīng)過編譯后,會在同步塊的前后分別形成monitorenter和monitorexit兩個字節(jié)碼指令。

每個鎖對象內(nèi)部維護一個計數(shù)器,該計數(shù)器初始值為0,表示任何線程都可以獲取該鎖并執(zhí)行相應(yīng)的方法。

根據(jù)虛擬機規(guī)范要求,在執(zhí)行monitorenter指令時,首先要嘗試獲取對象的鎖,如果這個對象沒有被鎖定,或者當(dāng)前線程已經(jīng)擁有了對象的鎖,把鎖的計數(shù)器+1,相應(yīng)的在執(zhí)行monitorexit指令后鎖計數(shù)器-1,當(dāng)計數(shù)器為0時,鎖就被釋放。

如果獲取對象鎖失敗,那當(dāng)前線程就要阻塞等待,直到對象鎖被另一個線程釋放為止。

(3)ReentrantLock如何實現(xiàn)可重入性

ReentrantLock使用內(nèi)部類Sync來管理鎖,所以真正的獲取鎖是由Sync的實現(xiàn)類控制的。

Sync有兩個實現(xiàn),分別為NonfairSync(非公公平鎖)和FairSync(公平鎖)。Sync通過繼承AQS實現(xiàn),在AQS中維護了一個private volatile int state來計算重入次數(shù),避免頻繁的持有釋放操作帶來的線程問題。

(4)ReentrantLock代碼實例

// Sync繼承于AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
  ...
}
// ReentrantLock默認(rèn)是非公平鎖
public ReentrantLock() {
        sync = new NonfairSync();
 }
// 可以通過向構(gòu)造方法中傳true來實現(xiàn)公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
protected final boolean tryAcquire(int acquires) {
        // 當(dāng)前想要獲取鎖的線程
        final Thread current = Thread.currentThread();
        // 當(dāng)前鎖的狀態(tài)
        int c = getState();
        // state == 0 此時此刻沒有線程持有鎖
        if (c == 0) {
            // 雖然此時此刻鎖是可以用的,但是這是公平鎖,既然是公平,就得講究先來后到,
            // 看看有沒有別人在隊列中等了半天了
            if (!hasQueuedPredecessors() &&
                // 如果沒有線程在等待,那就用CAS嘗試一下,成功了就獲取到鎖了,
                // 不成功的話,只能說明一個問題,就在剛剛幾乎同一時刻有個線程搶先了 =_=
                // 因為剛剛還沒人的,我判斷過了
                compareAndSetState(0, acquires)) {
 
                // 到這里就是獲取到鎖了,標(biāo)記一下,告訴大家,現(xiàn)在是我占用了鎖
                setExclusiveOwnerThread(current);
                return true;
            }
        }
          // 會進入這個else if分支,說明是重入了,需要操作:state=state+1
        // 這里不存在并發(fā)問題
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        // 如果到這里,說明前面的if和else if都沒有返回true,說明沒有獲取到鎖
        return false;
    }

(5)代碼分析

當(dāng)一個線程在獲取鎖過程中,先判斷state的值是否為0,如果是表示沒有線程持有鎖,就可以嘗試獲取鎖。

當(dāng)state的值不為0時,表示鎖已經(jīng)被一個線程占用了,這時會做一個判斷current==getExclusiveOwnerThread(),這個方法返回的是當(dāng)前持有鎖的線程,這個判斷是看當(dāng)前持有鎖的線程是不是自己,如果是自己,那么將state的值+1,表示重入返回即可。

69、什么是鎖消除和鎖粗化?

(1)鎖消除

所消除就是虛擬機根據(jù)一個對象是否真正存在同步情況,若不存在同步情況,則對該對象的訪問無需經(jīng)過加鎖解鎖的操作。

比如StringBuffer的append方法,因為append方法需要判斷對象是否被占用,而如果代碼不存在鎖競爭,那么這部分的性能消耗是無意義的。

于是虛擬機在即時編譯的時候就會將上面的代碼進行優(yōu)化,也就是鎖消除。

@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

從源碼可以看出,append方法用了 synchronized關(guān)鍵字,它是線程安全的。

但我們可能僅在線程內(nèi)部把StringBuffer當(dāng)做局部變量使用;StringBuffer僅在方法內(nèi)作用域有效,不存在線程安全的問題,這時我們可以通過編譯器將其優(yōu)化,將鎖消除

前提是Java必須運行在server模式,同時必須開啟逃逸分析;

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
 
其中+DoEscapeAnalysis表示開啟逃逸分析,+EliminateLocks表示鎖消除。
public static String createStringBuffer(String str1, String str2) {
    StringBuffer sBuf = new StringBuffer();
    sBuf.append(str1);// append方法是同步操作
    sBuf.append(str2);
    return sBuf.toString();
}

逃逸分析:比如上面的代碼,它要看sBuf是否可能逃出它的作用域?如果將sBuf作為方法的返回值進行返回,那么它在方法外部可能被當(dāng)作一個全局對象使用,就有可能發(fā)生線程安全問題,這時就可以說sBuf這個對象發(fā)生逃逸了,因而不應(yīng)將append操作的鎖消除,但我們上面的代碼沒有發(fā)生鎖逃逸,鎖消除就可以帶來一定的性能提升。 

(2)鎖粗化

鎖的請求、同步、釋放都會消耗一定的系統(tǒng)資源,如果高頻的鎖請求反而不利于系統(tǒng)性能的優(yōu)化,鎖粗化就是把多次的鎖請求合并成一個請求,擴大鎖的范圍,降低鎖請求、同步、釋放帶來的性能損耗。

70、跟 Synchronized 相比,可重入鎖 ReentrantLock 其實現(xiàn)原理有什么不同?

(1)都是可重入鎖;

(2)ReentrantLock內(nèi)部是實現(xiàn)了Sync,Sync繼承于AQS抽象類。

Sync有兩個實現(xiàn),一個是公平鎖,一個是非公平鎖,通過構(gòu)造函數(shù)定義。

AQS中維護了一個state來計算重入次數(shù),避免頻繁的持有釋放操作帶來的線程問題。

(3)ReentrantLock只能定義代碼塊,而Synchronized可以定義方法和代碼塊;

(4)Synchronized是JVM的一個內(nèi)部關(guān)鍵字,ReentrantLock是JDK1.5之后引入的一個API層面的互斥鎖;

(5)Synchronized實現(xiàn)自動的加鎖、釋放鎖,ReentrantLock需要手動加鎖和釋放鎖,中間可以暫停;

(6)Synchronized由于引進了偏向鎖和自旋鎖,所以性能上和ReentrantLock差不多,但操作上方便很多,所以優(yōu)先使用Synchronized。

71、那么請談?wù)?AQS 框架是怎么回事兒?

(1)AQS是AbstractQueuedSynchronizer的縮寫,它提供了一個FIFO隊列,可以看成是一個實現(xiàn)同步鎖的核心組件。

AQS是一個抽象類,主要通過繼承的方式來使用,它本身沒有實現(xiàn)任何的同步接口,僅僅是定義了同步狀態(tài)的獲取和釋放的方法來提供自定義的同步組件。

(2)AQS的兩種功能:獨占鎖和共享鎖

(3)AQS的內(nèi)部實現(xiàn)

AQS的實現(xiàn)依賴內(nèi)部的同步隊列,也就是FIFO的雙向隊列,如果當(dāng)前線程競爭失敗,那么AQS會把當(dāng)前線程以及等待狀態(tài)信息構(gòu)造成一個Node加入到同步隊列中,同時再阻塞該線程。當(dāng)獲取鎖的線程釋放鎖以后,會從隊列中喚醒一個阻塞的節(jié)點(線程)。

AQS隊列內(nèi)部維護的是一個FIFO的雙向鏈表,這種結(jié)構(gòu)的特點是每個數(shù)據(jù)結(jié)構(gòu)都有兩個指針,分別指向直接的后繼節(jié)點和直接前驅(qū)節(jié)點。

所以雙向鏈表可以從任意一個節(jié)點開始很方便的范文前驅(qū)和后繼節(jié)點。每個Node其實是由線程封裝,當(dāng)線程爭搶鎖失敗后會封裝成Node加入到AQS隊列中。

72、AQS 對資源的共享方式?

AQS定義兩種資源共享方式

(1)Exclusive(獨占)

只有一個線程能執(zhí)行,如ReentrantLock。又可分為公平鎖和非公平鎖:

  • 公平鎖:按照線程在隊列中的排隊順序,先到者先拿到鎖
  • 非公平鎖:當(dāng)線程要獲取鎖時,無視隊列順序直接去搶鎖,誰搶到就是誰的

(2)Share(共享)

多個線程可同時執(zhí)行,如Semaphore/CountDownLatch。

Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我們都會在后面講到。

ReentrantReadWriteLock 可以看成是組合式,因為ReentrantReadWriteLock也就是讀寫鎖允許多個線程同時對某一資源進行讀。

不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現(xiàn)時只需要實現(xiàn)共享資源 state 的獲取與釋放方式即可,至于具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經(jīng)在頂層實現(xiàn)好了。

73、如何讓 Java 的線程彼此同步?

  1. synchronized
  2. volatile
  3. ReenreantLock
  4. 使用局部變量實現(xiàn)線程同步

74、你了解過哪些同步器?請分別介紹下。

(1)Semaphore同步器

特征:

經(jīng)典的信號量,通過計數(shù)器控制對共享資源的訪問

Semaphore(int count):創(chuàng)建擁有count個許可證的信號量

acquire()/acquire(int num) : 獲取1/num個許可證

release/release(int num) : 釋放1/num個許可證

(2)CountDownLatch同步器

特征:

必須發(fā)生指定數(shù)量的事件后才可以繼續(xù)運行(比如賽跑比賽,裁判喊出3,2,1之后大家才同時跑)

CountDownLatch(int count):必須發(fā)生count個數(shù)量才可以打開鎖存器

await:等待鎖存器

countDown:觸發(fā)事件

(3)CyclicBarrier同步器

特征:

適用于只有多個線程都到達預(yù)定點時才可以繼續(xù)執(zhí)行(比如斗地主,需要等齊三個人才開始)

CyclicBarrier(int num) :等待線程的數(shù)量

CyclicBarrier(int num, Runnable action) :等待線程的數(shù)量以及所有線程到達后的操作

await() : 到達臨界點后暫停線程

(4)交換器(Exchanger)同步器

(5)Phaser同步器

75、Java 中的線程池是如何實現(xiàn)的

創(chuàng)建一個阻塞隊列來容納任務(wù),在第一次執(zhí)行任務(wù)時創(chuàng)建足夠多的線程,并處理任務(wù),之后每個工作線程自動從任務(wù)隊列中獲取線程,直到任務(wù)隊列中任務(wù)為0為止,此時線程處于等待狀態(tài),一旦有工作任務(wù)加入任務(wù)隊列中,即刻喚醒工作線程進行處理,實現(xiàn)線程的可復(fù)用性。

線程池一般包括四個基本組成部分:

(1)線程池管理器

用于創(chuàng)建線程池,銷毀線程池,添加新任務(wù)。

(2)工作線程

線程池中線程,可循環(huán)執(zhí)行任務(wù),在沒有任務(wù)時處于等待狀態(tài)。

(3)任務(wù)隊列

用于存放沒有處理的任務(wù),一種緩存機制。

(4)任務(wù)接口

每個任務(wù)必須實現(xiàn)的接口,供工作線程調(diào)度任務(wù)的執(zhí)行,主要規(guī)定了任務(wù)的開始和收尾工作,和任務(wù)的狀態(tài)。

76、創(chuàng)建線程池的幾個核心構(gòu)造參數(shù)

// Java線程池的完整構(gòu)造函數(shù)
public ThreadPoolExecutor(
  int corePoolSize, // 線程池長期維持的最小線程數(shù),即使線程處于Idle狀態(tài),也不會回收。
  int maximumPoolSize, // 線程數(shù)的上限
  long keepAliveTime, // 線程最大生命周期。
  TimeUnit unit, //時間單位                                 
  BlockingQueue<Runnable> workQueue, //任務(wù)隊列。當(dāng)線程池中的線程都處于運行狀態(tài),而此時任務(wù)數(shù)量繼續(xù)增加,則需要一個容器來容納這些任務(wù),這就是任務(wù)隊列。
  ThreadFactory threadFactory, // 線程工廠。定義如何啟動一個線程,可以設(shè)置線程名稱,并且可以確認(rèn)是否是后臺線程等。
  RejectedExecutionHandler handler // 拒絕任務(wù)處理器。由于超出線程數(shù)量和隊列容量而對繼續(xù)增加的任務(wù)進行處理的程序。
)

77、線程池中的線程是怎么創(chuàng)建的?是一開始就隨著線程池的啟動創(chuàng)建好的嗎?

線程池中的線程是在第一次提交任務(wù)submit時創(chuàng)建的

創(chuàng)建線程的方式有繼承Thread和實現(xiàn)Runnable,重寫run方法,start開始執(zhí)行,wait等待,sleep休眠,shutdown停止。

(1)newSingleThreadExecutor:單線程池。

顧名思義就是一個池中只有一個線程在運行,該線程永不超時,而且由于是一個線程,當(dāng)有多個任務(wù)需要處理時,會將它們放置到一個無界阻塞隊列中逐個處理,它的實現(xiàn)代碼如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
             new LinkedBlockingQueue<Runnable()));
}

它的使用方法也很簡單,下面是簡單的示例:

public static void main(String[] args) throws ExecutionException,InterruptedException {
    // 創(chuàng)建單線程執(zhí)行器
    ExecutorService es = Executors.newSingleThreadExecutor();
    // 執(zhí)行一個任務(wù)
    Future<String> future = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "";
        }
    });
    // 獲得任務(wù)執(zhí)行后的返回值
    System.out.println("返回值:" + future.get());
    // 關(guān)閉執(zhí)行器
    es.shutdown();
}

(2)newCachedThreadPool:緩沖功能的線程。

建立了一個線程池,而且線程數(shù)量是沒有限制的(當(dāng)然,不能超過Integer的最大值),新增一個任務(wù)即有一個線程處理,或者復(fù)用之前空閑的線程,或者重親啟動一個線程,但是一旦一個線程在60秒內(nèi)一直處于等待狀態(tài)時(也就是一分鐘無事可做),則會被終止,其源碼如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

這里需要說明的是,任務(wù)隊列使用了同步阻塞隊列,這意味著向隊列中加入一個元素,即可喚醒一個線程(新創(chuàng)建的線程或復(fù)用空閑線程來處理),這種隊列已經(jīng)沒有隊列深度的概念了。

(3)newFixedThreadPool:固定線程數(shù)量的線程池。

在初始化時已經(jīng)決定了線程的最大數(shù)量,若任務(wù)添加的能力超出了線程的處理能力,則建立阻塞隊列容納多余的任務(wù),其源碼如下: 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

上面返回的是一個ThreadPoolExecutor,它的corePoolSize和maximumPoolSize是相等的,也就是說,最大線程數(shù)量為nThreads。

如果任務(wù)增長的速度非???,超過了LinkedBlockingQuene的最大容量(Integer的最大值),那此時會如何處理呢?

會按照ThreadPoolExecutor默認(rèn)的拒絕策略(默認(rèn)是DiscardPolicy,直接丟棄)來處理。

以上三種線程池執(zhí)行器都是ThreadPoolExecutor的簡化版,目的是幫助開發(fā)人員屏蔽過得線程細(xì)節(jié),簡化多線程開發(fā)。

當(dāng)需要運行異步任務(wù)時,可以直接通過Executors獲得一個線程池,然后運行任務(wù),不需要關(guān)注ThreadPoolExecutor的一系列參數(shù)時什么含義。

當(dāng)然,有時候這三個線程不能滿足要求,此時則可以直接操作ThreadPoolExecutor來實現(xiàn)復(fù)雜的多線程計算。

newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool是線程池的簡化版,而ThreadPoolExecutor則是旗艦版___簡化版容易操作,需要了解的知識相對少些,方便使用,而旗艦版功能齊全,適用面廣,難以駕馭。

78、volatile 關(guān)鍵字的作用

對于可見性,Java 提供了 volatile 關(guān)鍵字來保證可見性和禁止指令重排。

volatile 提供 happens-before 的保證,確保一個線程的修改能對其他線程是可見的。當(dāng)一個共享變量被 volatile 修飾時,它會保證修改的值會立即被更新到主存,當(dāng)有其他線程需要讀取時,它會去內(nèi)存中讀取新值。

從實踐角度而言,volatile 的一個重要作用就是和 CAS 結(jié)合,保證了原子性,詳細(xì)的可以參見 java.util.concurrent.atomic 包下的類,比如 AtomicInteger。

volatile 常用于多線程環(huán)境下的單次操作(單次讀或者單次寫)。

79、既然 volatile 能夠保證線程間的變量可見性,是不是就意味著基于 volatile 變量的運算就是并發(fā)安全的?

volatile修飾的變量在各個線程的工作內(nèi)存中不存在一致性的問題(在各個線程工作的內(nèi)存中,volatile修飾的變量也會存在不一致的情況,但是由于每次使用之前都會先刷新主存中的數(shù)據(jù)到工作內(nèi)存,執(zhí)行引擎看不到不一致的情況,因此可以認(rèn)為不存在不一致的問題),但是java的運算并非原子性的操作,導(dǎo)致volatile在并發(fā)下并非是線程安全的。

80、ThreadLocal 是什么?有哪些使用場景?

ThreadLocal 是一個本地線程副本變量工具類,在每個線程中都創(chuàng)建了一個 ThreadLocalMap 對象

簡單說 ThreadLocal 就是一種以空間換時間的做法,每個線程可以訪問自己內(nèi)部 ThreadLocalMap 對象內(nèi)的 value。通過這種方式,避免資源在多線程間共享。

原理:線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個線程間共享。

Java提供ThreadLocal類來支持線程局部變量,是一種實現(xiàn)線程安全的方式。但是在管理環(huán)境下(如 web 服務(wù)器)使用線程局部變量的時候要特別小心,在這種情況下,工作線程的生命周期比任何應(yīng)用變量的生命周期都要長。

任何線程局部變量一旦在工作完成后沒有釋放,Java 應(yīng)用就存在內(nèi)存泄露的風(fēng)險。

經(jīng)典的使用場景是為每個線程分配一個 JDBC 連接 Connection。

這樣就可以保證每個線程的都在各自的 Connection 上進行數(shù)據(jù)庫的操作,不會出現(xiàn) A 線程關(guān)了 B線程正在使用的 Connection; 還有 Session 管理 等問題。

81、請談?wù)?ThreadLocal 是怎么解決并發(fā)安全的?

在java程序中,常用的有兩種機制來解決多線程并發(fā)問題,一種是sychronized方式,通過鎖機制,一個線程執(zhí)行時,讓另一個線程等待,是以時間換空間的方式來讓多線程串行執(zhí)行。

而另外一種方式就是ThreadLocal方式,通過創(chuàng)建線程局部變量,以空間換時間的方式來讓多線程并行執(zhí)行。兩種方式各有優(yōu)劣,適用于不同的場景,要根據(jù)不同的業(yè)務(wù)場景來進行選擇。

在spring的源碼中,就使用了ThreadLocal來管理連接,在很多開源項目中,都經(jīng)常使用ThreadLocal來控制多線程并發(fā)問題,因為它足夠的簡單,我們不需要關(guān)心是否有線程安全問題,因為變量是每個線程所特有的。

82、很多人都說要慎用 ThreadLocal,談?wù)勀愕睦斫?,使?ThreadLocal 需要注意些什么?

ThreadLocal 變量解決了多線程環(huán)境下單個線程中變量的共享問題,使用名為ThreadLocalMap的哈希表進行維護(key為ThreadLocal變量名,value為ThreadLocal變量的值);

使用時需要注意以下幾點:

  • 線程之間的threadLocal變量是互不影響的
  • 使用private final static進行修飾,防止多實例時內(nèi)存的泄露問題
  • 線程池環(huán)境下使用后將threadLocal變量remove掉或設(shè)置成一個初始值

83、為什么代碼會重排序?

在執(zhí)行程序時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎么排序就怎么排序

它需要滿足以下兩個條件:

  • 在單線程環(huán)境下不能改變程序運行的結(jié)果;
  • 存在數(shù)據(jù)依賴關(guān)系的不允許重排序

需要注意的是:重排序不會影響單線程環(huán)境的執(zhí)行結(jié)果,但是會破壞多線程的執(zhí)行語義。

84、什么是自旋

很多 synchronized 里面的代碼只是一些很簡單的代碼,執(zhí)行時間非???,此時等待的線程都加鎖可能是一種不太值得的操作,因為線程阻塞涉及到用戶態(tài)和內(nèi)核態(tài)切換的問題。

既然 synchronized 里面的代碼執(zhí)行得非常快,不妨讓等待鎖的線程不要被阻塞,而是在 synchronized 的邊界做忙循環(huán),這就是自旋。

如果做了多次循環(huán)發(fā)現(xiàn)還沒有獲得鎖,再阻塞,這樣可能是一種更好的策略。

85、多線程中 synchronized 鎖升級的原理是什么?

synchronized 鎖升級原理:在鎖對象的對象頭里面有一個 threadid 字段,在第一次訪問的時候 threadid 為空,jvm 讓其持有偏向鎖,并將 threadid 設(shè)置為其線程 id,再次進入的時候會先判斷 threadid 是否與其線程 id 一致

如果一致則可以直接使用此對象,如果不一致,則升級偏向鎖為輕量級鎖,通過自旋循環(huán)一定次數(shù)來獲取鎖,執(zhí)行一定次數(shù)之后,如果還沒有正常獲取到要使用的對象

此時就會把鎖從輕量級升級為重量級鎖,此過程就構(gòu)成了 synchronized 鎖的升級。

鎖的升級的目的:鎖升級是為了減低了鎖帶來的性能消耗。

在 Java 6 之后優(yōu)化 synchronized 的實現(xiàn)方式,使用了偏向鎖升級為輕量級鎖再升級到重量級鎖的方式,從而減低了鎖帶來的性能消耗。

86、synchronized 和 ReentrantLock 區(qū)別是什么?

synchronized 是和 if、else、for、while 一樣的關(guān)鍵字,ReentrantLock 是類,這是二者的本質(zhì)區(qū)別。

既然 ReentrantLock 是類,那么它就提供了比synchronized 更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量

synchronized 早期的實現(xiàn)比較低效,對比 ReentrantLock,大多數(shù)場景性能都相差較大,但是在 Java 6 中對 synchronized 進行了非常多的改進。

相同點:兩者都是可重入鎖

兩者都是可重入鎖。“可重入鎖”概念是:自己可以再次獲取自己的內(nèi)部鎖。

比如一個線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當(dāng)其再次想要獲取這個對象的鎖的時候還是可以獲取的,如果不可鎖重入的話,就會造成死鎖。

同一個線程每次獲取鎖,鎖的計數(shù)器都自增1,所以要等到鎖的計數(shù)器下降為0時才能釋放鎖。

主要區(qū)別如下:

  • ReentrantLock 使用起來比較靈活,但是必須有釋放鎖的配合動作;
  • ReentrantLock 必須手動獲取與釋放鎖,而 synchronized 不需要手動釋放和開啟鎖;
  • ReentrantLock 只適用于代碼塊鎖,而 synchronized 可以修飾類、方法、變量等。
  • 二者的鎖機制其實也是不一樣的。ReentrantLock 底層調(diào)用的是 Unsafe 的park 方法加鎖,synchronized 操作的應(yīng)該是對象頭中 mark word

Java中每一個對象都可以作為鎖,這是synchronized實現(xiàn)同步的基礎(chǔ):

  • 普通同步方法,鎖是當(dāng)前實例對象
  • 靜態(tài)同步方法,鎖是當(dāng)前類的class對象
  • 同步方法塊,鎖是括號里面的對象

87、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?對比同步它有什么優(yōu)勢?

Lock 接口比同步方法和同步塊提供了更具擴展性的鎖操作。

他們允許更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個相關(guān)類的條件對象。

它的優(yōu)勢有:

(1)可以使鎖更公平

(2)可以使線程在等待鎖的時候響應(yīng)中斷

(3)可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間

(4)可以在不同的范圍,以不同的順序獲取和釋放鎖

整體上來說 Lock 是 synchronized 的擴展版,Lock 提供了無條件的、可輪詢的(tryLock 方法)、定時的(tryLock 帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition 方法)鎖操作。

另外 Lock 的實現(xiàn)類基本都支持非公平鎖(默認(rèn))和公平鎖,synchronized 只支持非公平鎖,當(dāng)然,在大部分情況下,非公平鎖是高效的選擇。

88、jsp 和 servlet 有什么區(qū)別?

(1)servlet是服務(wù)器端的Java程序,它擔(dān)當(dāng)客戶端和服務(wù)端的中間層。

(2)jsp全名為Java server pages,中文名叫Java服務(wù)器頁面,其本質(zhì)是一個簡化的servlet設(shè)計。JSP是一種動態(tài)頁面設(shè)計,它的主要目的是將表示邏輯從servlet中分離出來。

(3)JVM只能識別Java代碼,不能識別JSP,JSP編譯后變成了servlet,web容器將JSP的代碼編譯成JVM能夠識別的Java類(servlet)。

(4)JSP有內(nèi)置對象、servlet沒有內(nèi)置對象。

89、jsp 有哪些內(nèi)置對象?作用分別是什么?

JSP九大內(nèi)置對象:

  1. pageContext,頁面上下文對象,相當(dāng)于頁面中所有功能的集合,通過它可以獲取JSP頁面的out、request、response、session、application對象。
  2. request
  3. response
  4. session
  5. application,應(yīng)用程序?qū)ο?,application實現(xiàn)了用戶間數(shù)據(jù)的共享,可存放全局變量,它開始于服務(wù)器啟動,知道服務(wù)器關(guān)閉。
  6. page,就是JSP本身。
  7. exception
  8. out,out用于在web瀏覽器內(nèi)輸出信息,并且管理應(yīng)用服務(wù)器上的輸出緩沖區(qū),作用域page。
  9. config,取得服務(wù)器的配置信息。

90、forward 和 redirect 的區(qū)別?

  1. forward是直接請求轉(zhuǎn)發(fā);redirect是間接請求轉(zhuǎn)發(fā),又叫重定向。
  2. forward,客戶端和瀏覽器執(zhí)行一次請求;redirect,客戶端和瀏覽器執(zhí)行兩次請求。
  3. forward,經(jīng)典的MVC模式就是forward;redirect,用于避免用戶的非正常訪問。(例如用戶非正常訪問,servlet就可以將HTTP請求重定向到登錄頁面)。
  4. forward,地址不變;redirect,地址改變。
  5. forward常用方法:RequestDispatcher類的forward()方法;redirect常用方法:HttpServletRequest類的sendRedirect()方法。

91、說一下 jsp 的 4 種作用域?

application、session、request、page

92、session 和 cookie 有什么區(qū)別?

(1)存儲位置不同

  • cookie在客戶端瀏覽器;
  • session在服務(wù)器;

(2)存儲容量不同

  • cookie<=4K,一個站點最多保留20個cookie;
  • session沒有上線,出于對服務(wù)器的保護,session內(nèi)不可存過多東西,并且要設(shè)置session刪除機制;

(3)存儲方式不同

  • cookie只能保存ASCII字符串,并需要通過編碼方式存儲為Unicode字符或者二進制數(shù)據(jù);
  • session中能存儲任何類型的數(shù)據(jù),包括并不局限于String、integer、list、map等;

(4)隱私策略不同

  • cookie對客戶端是可見的,不安全;
  • session存儲在服務(wù)器上,安全;

(5)有效期不同

  • 開發(fā)可以通過設(shè)置cookie的屬性,達到使cookie長期有效的效果;
  • session依賴于名為JESSIONID的cookie,而cookie JSESSIONID的過期時間默認(rèn)為-1,只需關(guān)閉窗口該session就會失效,因而session達不到長期有效的效果;

(6)跨域支持上不同

  • cookie支持跨域;
  • session不支持跨域;

93、如果客戶端禁止 cookie 能實現(xiàn) session 還能用嗎?

一般默認(rèn)情況下,在會話中,服務(wù)器存儲 session 的 sessionid 是通過 cookie 存到瀏覽器里。

如果瀏覽器禁用了 cookie,瀏覽器請求服務(wù)器無法攜帶 sessionid,服務(wù)器無法識別請求中的用戶身份,session失效。

但是可以通過其他方法在禁用 cookie 的情況下,可以繼續(xù)使用session。

  1. 通過url重寫,把 sessionid 作為參數(shù)追加的原 url 中,后續(xù)的瀏覽器與服務(wù)器交互中攜帶 sessionid 參數(shù)。
  2. 服務(wù)器的返回數(shù)據(jù)中包含 sessionid,瀏覽器發(fā)送請求時,攜帶 sessionid 參數(shù)。
  3. 通過 Http 協(xié)議其他 header 字段,服務(wù)器每次返回時設(shè)置該 header 字段信息,瀏覽器中 js 讀取該 header 字段,請求服務(wù)器時,js設(shè)置攜帶該 header 字段。

94、什么是上下文切換?

多線程編程中一般線程的個數(shù)都大于 CPU 核心的個數(shù),而一個 CPU 核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效執(zhí)行,CPU 采取的策略是為每個線程分配時間片并輪轉(zhuǎn)的形式。

當(dāng)一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使用,這個過程就屬于一次上下文切換。

概括來說就是:

  • 當(dāng)前任務(wù)在執(zhí)行完 CPU 時間片切換到另一個任務(wù)之前會先保存自己的狀態(tài),以便下次再切換回這個任務(wù)時,可以再加載這個任務(wù)的狀態(tài)。
  • 任務(wù)從保存到再加載的過程就是一次上下文切換。

上下文切換通常是計算密集型的。也就是說,它需要相當(dāng)可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。

所以,上下文切換對系統(tǒng)來說意味著消耗大量的 CPU 時間,事實上,可能是操作系統(tǒng)中時間消耗最大的操作。

Linux 相比與其他操作系統(tǒng)(包括其他類 Unix 系統(tǒng))有很多的優(yōu)點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

95、cookie、session、token

1、session機制

session是服務(wù)端存儲的一個對象,主要用來存儲所有訪問過該服務(wù)端的客戶端的用戶信息(也可以存儲其他信息),從而實現(xiàn)保持用戶會話狀態(tài)。

但是服務(wù)器重啟時,內(nèi)存會被銷毀,存儲的用戶信息也就消失了。

不同的用戶訪問服務(wù)端的時候會在session對象中存儲鍵值對,“鍵”用來存儲開啟這個用戶信息的“鑰匙”,在登錄成功后,“鑰匙”通過cookie返回給客戶端,客戶端存儲為sessionId記錄在cookie中。

當(dāng)客戶端再次訪問時,會默認(rèn)攜帶cookie中的sessionId來實現(xiàn)會話機制。

(1)session是基于cookie的。

  • cookie的數(shù)據(jù)4k左右;
  • cookie存儲數(shù)據(jù)的格式:字符串key=value
  • cookie存儲有效期:可以自行通過expires進行具體的日期設(shè)置,如果沒設(shè)置,默認(rèn)是關(guān)閉瀏覽器時失效。
  • cookie有效范圍:當(dāng)前域名下有效。所以session這種會話存儲方式方式只適用于客戶端代碼和服務(wù)端代碼運行在同一臺服務(wù)器上(前后端項目協(xié)議、域名、端口號都一致,即在一個項目下)

(2)session持久化

用于解決重啟服務(wù)器后session消失的問題。在數(shù)據(jù)庫中存儲session,而不是存儲在內(nèi)存中。通過包:express-mysql-session。

當(dāng)客戶端存儲的cookie失效后,服務(wù)端的session不會立即銷毀,會有一個延時,服務(wù)端會定期清理無效session,不會造成無效數(shù)據(jù)占用存儲空間的問題。

2、token機制

適用于前后端分離的項目(前后端代碼運行在不同的服務(wù)器下)

請求登錄時,token和sessionid原理相同,是對key和key對應(yīng)的用戶信息進行加密后的加密字符,登錄成功后,會在響應(yīng)主體中將{token:“字符串”}返回給客戶端。

客戶端通過cookie都可以進行存儲。

再次請求時不會默認(rèn)攜帶,需要在請求攔截器位置給請求頭中添加認(rèn)證字段Authorization攜帶token信息,服務(wù)器就可以通過token信息查找用戶登錄狀態(tài)。

96、說一下 session 的工作原理?

當(dāng)客戶端登錄完成后,會在服務(wù)端產(chǎn)生一個session,此時服務(wù)端會將sessionid返回給客戶端瀏覽器。

客戶端將sessionid儲存在瀏覽器的cookie中,當(dāng)用戶再次登錄時,會獲得對應(yīng)的sessionid,然后將sessionid發(fā)送到服務(wù)端請求登錄,服務(wù)端在內(nèi)存中找到對應(yīng)的sessionid,完成登錄,如果找不到,返回登錄頁面。

97、http 響應(yīng)碼 301 和 302 代表的是什么?有什么區(qū)別?

  1. 301和302狀態(tài)碼都表示重定向,當(dāng)瀏覽器拿到服務(wù)器返回的這個狀態(tài)碼后悔自動跳轉(zhuǎn)到一個新的URL地址。
  2. 301代表永久性重定向,舊地址被永久移除,客戶端向新地址發(fā)送請求。
  3. 302代表暫時性重定向,舊地址還在,客戶端繼續(xù)向舊地址發(fā)送請求。
  4. 303代表暫時性重定向,重定向到新地址時,必須使用GET方法請求新地址。
  5. 307代表暫時性重定向,與302的區(qū)別在于307不允許從POST改為GET。
  6. 307代表永久性重定向,與301的區(qū)別在于308不允許從POST改為GET。

98、簡述 tcp 和 udp的區(qū)別?

  1. TCP是傳輸控制協(xié)議,UDP是用戶數(shù)據(jù)表協(xié)議;
  2. TCP長連接,UDP無連接;
  3. UDP程序結(jié)構(gòu)較簡單,只需發(fā)送,無須接收;
  4. TCP可靠,保證數(shù)據(jù)正確性、順序性;UDP不可靠,可能丟數(shù)據(jù);
  5. TCP適用于少量數(shù)據(jù),UDP適用于大量數(shù)據(jù)傳輸;
  6. TCP速度慢,UDP速度快;

99、tcp 為什么要三次握手,兩次不行嗎?為什么?

因為客戶端和服務(wù)端都要確認(rèn)連接,

①客戶端請求連接服務(wù)端;

②針對客戶端的請求確認(rèn)應(yīng)答,并請求建立連接;

③針對服務(wù)端的請求確認(rèn)應(yīng)答,建立連接;

兩次無法確保A能收到B的數(shù)據(jù);

100、OSI 的七層模型都有哪些?

101、get 和 post 請求有哪些區(qū)別?

  1. get請求參數(shù)是連接在url后面的,而post請求參數(shù)是存放在requestbody內(nèi)的;
  2. get請求因為瀏覽器對url長度有限制,所以參數(shù)個數(shù)有限制,而post請求參數(shù)個數(shù)沒有限制;
  3. 因為get請求參數(shù)暴露在url上,所以安全方面post比get更加安全;
  4. get請求只能進行url編碼,而post請求可以支持多種編碼方式;
  5. get請求參數(shù)會保存在瀏覽器歷史記錄內(nèi),post請求并不會;
  6. get請求瀏覽器會主動cache,post并不會,除非主動設(shè)置;
  7. get請求產(chǎn)生1個tcp數(shù)據(jù)包,post請求產(chǎn)生2個tcp數(shù)據(jù)包;
  8. 在瀏覽器進行回退操作時,get請求是無害的,而post請求則會重新請求一次;
  9. 瀏覽器在發(fā)送get請求時會將header和data一起發(fā)送給服務(wù)器,服務(wù)器返回200狀態(tài)碼,而在發(fā)送post請求時,會先將header發(fā)送給服務(wù)器,服務(wù)器返回100,之后再將data發(fā)送給服務(wù)器,服務(wù)器返回200 OK;

102、什么是 XSS 攻擊,如何避免?

xss(Cross Site Scripting),即跨站腳本攻擊,是一種常見于web應(yīng)用程序中的計算機安全漏洞。

指的是在用戶瀏覽器上,在渲染DOM樹的時候,執(zhí)行了不可預(yù)期的JS腳本,從而發(fā)生了安全問題。

XSS就是通過在用戶端注入惡意的可運行腳本,若服務(wù)端對用戶的輸入不進行處理,直接將用戶的輸入輸出到瀏覽器,然后瀏覽器將會執(zhí)行用戶注入的腳本。

所以XSS攻擊的核心就是瀏覽器渲染DOM的時候?qū)⑽谋拘畔⒔馕龀蒍S腳本從而引發(fā)JS腳本注入,那么XSS攻擊的防御手段就是基于瀏覽器渲染這一步去做防御。

只要我們使用HTML編碼將瀏覽器需要渲染的信息編碼后,瀏覽器在渲染DOM元素的時候,會自動解碼需要渲染的信息,將上述信息解析成字符串而不是JS腳本,這就是我們防御XSS攻擊的核心想法。

預(yù)防:

1、獲取用戶的輸入,不用innerHtml,用innerText.

2、對用戶的輸入進行過濾,如對& < > " ' /等進行轉(zhuǎn)義;

103、什么是 CSRF 攻擊,如何避免?

跨站請求偽造(英語:Cross-site request forgery),也被稱為 one-click attack 或者 session riding,通??s寫為 CSRF 或者 XSRF, 是一種挾制用戶在當(dāng)前已登錄的Web應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法。

跟跨網(wǎng)站腳本(XSS)相比,XSS 利用的是用戶對指定網(wǎng)站的信任,CSRF 利用的是網(wǎng)站對用戶網(wǎng)頁瀏覽器的信任。

1、攻擊細(xì)節(jié)

跨站請求攻擊,簡單地說,是攻擊者通過一些技術(shù)手段欺騙用戶的瀏覽器去訪問一個自己曾經(jīng)認(rèn)證過的網(wǎng)站并運行一些操作(如發(fā)郵件,發(fā)消息,甚至財產(chǎn)操作如轉(zhuǎn)賬和購買商品)。

由于瀏覽器曾經(jīng)認(rèn)證過,所以被訪問的網(wǎng)站會認(rèn)為是真正的用戶操作而去運行。

這利用了web中用戶身份驗證的一個漏洞:簡單的身份驗證只能保證請求發(fā)自某個用戶的瀏覽器,卻不能保證請求本身是用戶自愿發(fā)出的。

例子

假如一家銀行用以運行轉(zhuǎn)賬操作的URL地址如下:http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName

那么,一個惡意攻擊者可以在另一個網(wǎng)站上放置如下代碼: <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

如果有賬戶名為Alice的用戶訪問了惡意站點,而她之前剛訪問過銀行不久,登錄信息尚未過期,那么她就會損失1000資金。

這種惡意的網(wǎng)址可以有很多種形式,藏身于網(wǎng)頁中的許多地方。此外,攻擊者也不需要控制放置惡意網(wǎng)址的網(wǎng)站。

例如他可以將這種地址藏在論壇,博客等任何用戶生成信息的網(wǎng)站中。這意味著如果服務(wù)端沒有合適的防御措施的話,用戶即使訪問熟悉的可信網(wǎng)站也有受攻擊的危險。

透過例子能夠看出,攻擊者并不能通過CSRF攻擊來直接獲取用戶的賬戶控制權(quán),也不能直接竊取用戶的任何信息。

他們能做到的,是欺騙用戶瀏覽器,讓其以用戶的名義運行操作。

2、防御措施

檢查Referer字段

HTTP頭中有一個Referer字段,這個字段用以標(biāo)明請求來源于哪個地址。

在處理敏感數(shù)據(jù)請求時,通常來說,Referer字段應(yīng)和請求的地址位于同一域名下。以上文銀行操作為例,Referer字段地址通常應(yīng)該是轉(zhuǎn)賬按鈕所在的網(wǎng)頁地址,應(yīng)該也位于www.examplebank.com之下。

而如果是CSRF攻擊傳來的請求,Referer字段會是包含惡意網(wǎng)址的地址,不會位于www.examplebank.com之下,這時候服務(wù)器就能識別出惡意的訪問。

這種辦法簡單易行,工作量低,僅需要在關(guān)鍵訪問處增加一步校驗。

但這種辦法也有其局限性,因其完全依賴瀏覽器發(fā)送正確的Referer字段。

雖然http協(xié)議對此字段的內(nèi)容有明確的規(guī)定,但并無法保證來訪的瀏覽器的具體實現(xiàn),亦無法保證瀏覽器沒有安全漏洞影響到此字段。

并且也存在攻擊者攻擊某些瀏覽器,篡改其Referer字段的可能。

3、添加校驗token

由于CSRF的本質(zhì)在于攻擊者欺騙用戶去訪問自己設(shè)置的地址,所以如果要求在訪問敏感數(shù)據(jù)請求時,要求用戶瀏覽器提供不保存在cookie中,并且攻擊者無法偽造的數(shù)據(jù)作為校驗,那么攻擊者就無法再運行CSRF攻擊。

這種數(shù)據(jù)通常是窗體中的一個數(shù)據(jù)項。

服務(wù)器將其生成并附加在窗體中,其內(nèi)容是一個偽隨機數(shù)

。當(dāng)客戶端通過窗體提交請求時,這個偽隨機數(shù)也一并提交上去以供校驗。正常的訪問時,客戶端瀏覽器能夠正確得到并傳回這個偽隨機數(shù),而通過CSRF傳來的欺騙性攻擊中,攻擊者無從事先得知這個偽隨機數(shù)的值,服務(wù)端就會因為校驗token的值為空或者錯誤,拒絕這個可疑請求。

104、如何實現(xiàn)跨域?說一下 JSONP 實現(xiàn)原理?

1、jsonp原理詳解——終于搞清楚jsonp是啥了

2、最流行的跨域方案cors

cors是目前主流的跨域解決方案,跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應(yīng)用被準(zhǔn)許訪問來自不同源服務(wù)器上的指定的資源。

當(dāng)一個資源從與該資源本身所在的服務(wù)器不同的域、協(xié)議或端口請求一個資源時,資源會發(fā)起一個跨域 HTTP 請求。

3、最方便的跨域方案Nginx

nginx是一款極其強大的web服務(wù)器,其優(yōu)點就是輕量級、啟動快、高并發(fā)。

現(xiàn)在的新項目中nginx幾乎是首選,我們用node或者java開發(fā)的服務(wù)通常都需要經(jīng)過nginx的反向代理。

反向代理的原理很簡單,即所有客戶端的請求都必須先經(jīng)過nginx的處理,nginx作為代理服務(wù)器再講請求轉(zhuǎn)發(fā)給node或者java服務(wù),這樣就規(guī)避了同源策略。

到此這篇關(guān)于Java經(jīng)典面試題最全匯總208道(二)的文章就介紹到這了,更多相關(guān)Java面試題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA對象分析之偏向鎖、輕量級鎖、重量級鎖升級過程

    JAVA對象分析之偏向鎖、輕量級鎖、重量級鎖升級過程

    這篇文章主要介紹了JAVA對象分析之偏向鎖、輕量級鎖、重量級鎖升級過程,又對這方面感興趣的同學(xué)可以跟著一起研究下
    2021-02-02
  • Java設(shè)計模式之狀態(tài)模式State Pattern詳解

    Java設(shè)計模式之狀態(tài)模式State Pattern詳解

    這篇文章主要介紹了Java設(shè)計模式之狀態(tài)模式State Pattern,狀態(tài)模式允許一個對象在其內(nèi)部狀態(tài)改變的時候改變其行為。這個對象看上去就像是改變了它的類一樣
    2022-11-11
  • Spring體系的各種啟動流程詳解

    Spring體系的各種啟動流程詳解

    這篇文章主要給大家介紹了關(guān)于Spring體系的各種啟動流程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • 在Mac OS上安裝Tomcat服務(wù)器的教程

    在Mac OS上安裝Tomcat服務(wù)器的教程

    這篇文章主要介紹了在Mac OS上安裝Tomcat服務(wù)器的教程,方便進行工作環(huán)境下的Java web開發(fā),需要的朋友可以參考下
    2015-11-11
  • java解析excel文件的方法

    java解析excel文件的方法

    這篇文章主要介紹了java解析excel文件的方法,這里整理相關(guān)的代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • jdk自帶線程池實例詳解

    jdk自帶線程池實例詳解

    在最近做的一個項目中,需要大量的使用到多線程和線程池,下面就java自帶的線程池和大家一起分享
    2018-02-02
  • SpringBoot Session接口驗證實現(xiàn)流程詳解

    SpringBoot Session接口驗證實現(xiàn)流程詳解

    這篇文章主要介紹了SpringBoot+Session實現(xiàn)接口驗證(過濾器+攔截器)文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • solr 配置中文分析器/定義業(yè)務(wù)域/配置DataImport功能方法(測試用)

    solr 配置中文分析器/定義業(yè)務(wù)域/配置DataImport功能方法(測試用)

    下面小編就為大家?guī)硪黄猻olr 配置中文分析器/定義業(yè)務(wù)域/配置DataImport功能方法(測試用)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • IDEA強制清除Maven緩存的實現(xiàn)示例

    IDEA強制清除Maven緩存的實現(xiàn)示例

    清除項目緩存是一個常見的操作,本文主要介紹了IDEA強制清除Maven緩存的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • Java線程生命周期及轉(zhuǎn)換過程

    Java線程生命周期及轉(zhuǎn)換過程

    這篇文章主要介紹了Java線程生命周期及轉(zhuǎn)換過程,線程的生命周期指的是線程從創(chuàng)建到銷毀的整個過程初始狀態(tài)、可運行狀態(tài)、運行狀態(tài)、休眠狀態(tài)、終止?fàn)顟B(tài),更多詳細(xì)介紹,需要的小伙伴可以參考下面文章內(nèi)容
    2022-05-05

最新評論