2020求職筆記(三)

引子

有的地方,當你呼吸到那兒的空氣,你身體裏的每一根骨頭都會告訴你,你不喜歡。

今天是崔小胖和我戀愛六週年的紀念日。有她在,我的世界就有光,任他千難萬險,我都不會迷茫。加油吧,年輕人!

題目

1、如果一個對象作爲 HashMap 的Key,那這個對象需要做些什麼?在put一個鍵值對的時候,hashCode() 和 equals() 方法誰先被調用?兩個對象hashCode()相等,equals()就一定會返回 true麼?既然你說到了hash衝突,能詳細介紹一下麼?

如果一個對象作爲HashMap的Key,那麼我們需要重寫這個對象的 hashCode() 和 equals() 方法。

在put一個鍵值對的時候時候,hashCode() 先調用;put(K key, V value) 底層實際調用的是 putVal(hash(key), key, value, false, true); 而這裏的 hash(key) 方法就會首先調用到 key.hashCode();

  • 兩個對象hashCode()相等,equals() 也不一定返回 true;
  • 兩個對象 hashCode()不等,equals() 一定返回 false;
  • 兩個對象 equals()返回true,hashCode() 一定相等;
  • 兩個對象 equals()返回false,hashCode() 可能相等,可能不等;

原因是,可能存在hash衝突,使得兩個不同的值在進行hash()函數計算過後,得到相同的hashCode;

對於hash衝突,常用的解決方案有兩大類:

  • 開放尋址法
  • 鏈表法

對於鏈表法,我們常見的 HashMap,就採用的是這種方式來解決hash衝突。而對於 開放尋址法,又可以分爲 線性探測法、二次探測法 和 雙重散列法 等。

2、一個線程,調用start()方法兩次,會有什麼結果?一個線程在執行中如果需要另一個線程的執行結果,怎麼處理?如果多個線程先後獲取到了同一把鎖,然後 wait(),你調用了 notifyAll() 了之後,這些線程怎麼辦?

如果對一個 Thread對象調用兩次 start()方法,第一次正常調用,第二次會報錯 IllegalThreadStateException

一個線程需要另一個線程的執行結果,可以採用下面兩種方式:

  • Future模式;
  • Object.wait()、Object.notify()

在調用 notifyAll() 之後,會喚醒所有正在該對象上等待的線程,這些線程都會參與鎖競爭,最終只有一個線程能持有鎖,未拿到鎖的,將阻塞。

3、Lock接口瞭解麼?和synchronized關鍵字有什麼區別?你提到了中斷,當我們調用方法後,線程會立即被中斷麼?synchonized關鍵字是如何實現同步的?鎖的可重入性是指什麼呢?怎麼實現的?你剛纔還說到了Condition,一個ReentrantLock可以綁定多個Condition麼?

對於Lock接口,我是結合着 ReentrantLock 來講的,參考:https://blog.csdn.net/Zereao/article/details/105627948#t4

線程中斷,涉及到的有三個方法:

// 爲調用線程設置一箇中斷標記
public void interrupt();
// 檢查調用線程是否被設置過中斷標記
public boolean isInterrupted()

// Thread類的靜態方法,判斷當前線程是否被中斷,並清除中斷狀態。如果連續兩次調用該方法,則第二次調用將返回 false
public static boolean interrupted();

前兩個是Thead類的實例方法,最後一個方法是Thread類的靜態方法。需要注意的是,當我們調用interrupt()方法後,只是給調用線程設置一箇中斷標記,並不會立即使線程中斷;至於目標線程要如何響應這個中斷標記,則需要自己去編碼做處理。

對於synchronized關鍵字,在javac編譯過後,會在同步塊的前後插入 monitorentermonitorexit 兩條字節碼指令。這兩條字節碼指令都需要指定一個 reference 類型的參數來指明要鎖定和解鎖的對象。如果 Java代碼中的 synchronized 明確指定了對象參數,那麼就以這個對象的引用作爲 reference;如果沒有明確指定,那就根據 synchronized 修飾的方法類型(實例方法還是類方法),來決定是取代碼所在的對象實例還是取類型對應的Class對象來作爲線程要持有的鎖。

鎖的可重入性是指一個線程能夠反覆進入被他自己持有鎖的同步塊。可重入性是通過鎖關聯的計數器來實現的:在執行 monitorenter 指令時,首先要去嘗試獲取對象的鎖。如果這個對象沒有被鎖定,或者這個對象已經持有了那個對象的鎖,就把鎖的計數器的值增加 1,而在執行 monitorexit 指令的時候計數器的值就會減1。一旦計數器的值爲0,鎖就會被釋放掉。

一個ReentrantLock可以綁定多個 condition,同時,一個ReentrantLock對象也可以多次調用 lock() 方法實現多次鎖定,但與之對應的是,釋放的時候也需要釋放多次。

4、你們項目中一般哪種線程池用的比較多?能解釋下 corePoolSize 和 maxmiumPoolSize 這兩個參數有什麼區別麼?有哪些拒絕策略可選呢?這幾個相關的配置,你們一般是怎麼決定大小配置多少呢?

常見的線程池都定義在 Executors類中,有下面三種:

  • newFixedThreadPool():返回一個固定線程數量的線程池。
  • newCacheTheadPool():返回一個可動態調整數量的線程池。
  • newScheduledThreadPool():返回一個可以給定時間執行任務的線程池。

此外還有兩個比較特殊的:

  • newSingleThreadExecutor:返回一個只有一個線程的線程池
  • newSingleThreadScheduledExecutor:只有一個線程的,可以給定時間執行任務的線程池

在我們的項目中,我們一般是通過ThreadPoolExecutor來自定義線程池的,上面幾種常見的線程池實際上也是 ThreadPoolExecutor 的封裝。

corePoolSize指定了通常情況下線程池中的線程數量。當我們向線程池中提交一個任務的時候:

  • 如果線程池中的線程數小於 corePoolSize,則會新建一個線程去執行任務;
  • 如果線程數等於corePoolSize,但是有的線程是空閒狀態,則複用空閒線程去執行任務;
  • 如果所有線程都被佔用,則當前線程進入等待隊列;
  • 如果等待隊列已滿,並且線程池中的線程數小於 maxmiumPoolSize 時,則會新建線程,去執行任務;
  • 如果線程池中的線程數等於 maxPoolSize,並且隊列已滿,則執行拒絕策略;

拒絕策略有四種:

  • AbortPolicy:直接拋出異常
  • CallerRunsPolicy:在調用者線程中運行被丟棄的任務
  • DiscardPolicy:默默丟棄任務,不予任何處理
  • DiscardOldestPolicy:丟棄最老的一個任務,也就是即將被執行的那個任務

線程池的幾個配置,我們一般都是根據經驗來定各項數值的大小的,考慮的因素就是 任務併發量、主機配置等。

5、Java內存模型能聊一下麼?這裏的工作內存相當於Java內存區域中的哪一塊呢?Java虛擬機棧中存放了哪些信息呢?volatile關鍵字能聊一下麼?內存屏障能聊一下麼?

Java內存模型規定,所有的變量都存儲在主內存中,每條線程都有自己的工作內存,工作內存保存了主內存中變量的副本。線程對變量的所有操作都必須在 工作內存 中,不能直接操作主內存中的變量。不同線程也不能訪問對象的工作內存,線程之間的同步必須要通過主內存來完成。

這裏的工作內存,相當於Java內存區域中的Java虛擬機棧,棧中存放了 局部變量表、操作數棧、動態鏈接、方法出口等信息。

對於volatile關鍵字,當一個變量被volatile修飾時,有兩個效果:

  1. 保證了此變量在多線程環境下的可見性。
  2. 禁止指令重排優化

第一點,保證可見性。在多線程環境下,各工作內存中的變量可能仍然是不一致的,但是由於每次使用前,都需要從主內存刷新,使用完後必須刷新會主內存,Java執行引擎就看不見這種不一致,變現出來就是當一個線程更改了這個值,對其他線程來說是立即可見的。

第二點,禁止指令重排優化。這是通過“內存屏障”來實現的。所謂內存屏障,是指在volatile修飾的變量在賦值完成後,會多執行一條 lock前綴 空操作指令,這個操作就相當於一個“內存屏障”。指令重排時,後邊的指令不能重排到內存屏障之前的位置。

對於lock前綴,其作用是 使本CPU的緩存寫入內存,同時使其他CPU也無效化其緩存。

6、Spring是如何解決循環依賴的問題的?如果是構造方式注入呢?

Spring是通過“三級緩存”來解決循環依賴的問題的。在Spring中定義了三個Map,來作爲緩存:

  • 一級緩存,singletonObjects,存放的是 已經實例化好的單例對象;
  • 二級緩存,earlySingletonObjects,存放的是 還沒組裝完畢就提前曝光的對象;
  • 三級緩存,singletonFactories,存放的是 即將要被實例化的對象的對象工廠;

當我們需要創建一個bean的時候,首先會從一級緩存singletonObjects中去嘗試獲取這個bean;如果沒有,則會嘗試去二級緩存earlySingletonObjects中獲取;如果也沒有,則會從三級緩存中去獲取,找到對應的工廠,獲取未完全填充完畢的bean。然後刪除三級緩存的數據,並將這個bean填充到二級緩存。

假如依賴關係是 A -> B -> A 這樣一個依賴關係。當需要獲取A的時候,從一級、二級緩存中獲取,都沒有,於是就從三級緩存獲取A,並將未完全填充完畢的A bean暴露到二級緩存。當繼續填充A的其他屬性的時候,發現A依賴了B。於是又從一級、二級緩存中去獲取B,也沒有,於是又從三級緩存獲取B,並繼續填充B的其他屬性。此時發現B又依賴了A,從一級緩存獲取A,沒有,又從二級緩存獲取A,將未完全填充完畢的A賦值給B。這樣B就填充完畢了,B會被放到一級緩存中去,同時刪除掉B的二級、三級緩存。B填充好了,A也就能填充好了。

如果是構造方式注入,Spring解決不了循環依賴的問題。Spring容器會將每一個正在創建的Bean的名稱放到一個 Set中。如果在Bean的創建過程中如果發現自己已經在創建中了,就會拋出 BeanCurrentlyInCreationException 異常。

所以,要解決循環依賴的問題,我們一般使用 屬性方式注入 或 setter方式注入。

7、docker網絡模型說一下?

當我們在使用 docker run 創建容器時,可以使用 --net 來指定容器的網絡模式。docker有下面4中網絡模式:

  • host模式:此時容器和宿主機共同同一個網絡空間,容器不會虛擬出自己的網卡、IP等。
  • container模式:此時新創建的容器和已經存在的容器共享一個網絡空間,但各自的文件系統、系統進程列表還是隔離的。
  • bridge模式:默認的模式,它會爲容器生成一個單獨的子網絡,每一個容器都有自己的網絡空間。
  • none模式:此時容器擁有自己的網絡空間,但是docker並不爲容器進行任何網絡配置。

8、TCP/IP四層架構和OSI七層架構之間是如何對應的?

9、CPU調度算法有哪些?內核態和用戶態有什麼區別?

CPU調度算法有下面幾種:

  • 先到先服務調度算法
  • 最短作業優先調度算法
  • 優先級調度算法
  • 時間片輪轉算法
  • 多級隊列調度
  • 多級反饋隊列調度

具體的詳情參考:https://www.cnblogs.com/PIRATE-JFZHOU/p/8094790.html

當一個進程運行用戶自己的代碼時,處於用戶態;當進程因爲系統調用執行內核代碼時,處於內核態。

參考文檔

1、《計算機網絡》第七版,謝希仁·著,第一章

2、https://www.cnblogs.com/PIRATE-JFZHOU/p/8094790.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章