Java老司機帶你刨面試題(集合與線程篇_標題黨請進)

史上最全面試題(集合與線程篇_標題黨請進)

1.1 Collection 和 Map

掌握Collection和Map的繼承體系(有時候他讓你畫)
image
image

1.2 List和Set區別

List,Set 都是繼承自 Collection 接口
List 特點:元素有放入順序,元素可重複
Set 特點:元素無放入順序,元素不可重複,重複元素會覆蓋掉
(注意:元素雖然無放入順序,但是元素在 set 中的位置是有該元素的HashCode 決定的,其位置其實是固定的,加入 Set 的 Object 必須定義 equals() 方法 ,另外 list 支持 for 循環,也就是通過下標來遍歷,也可以用迭代器,但是set 只能用迭代,因爲他無序,無法用下標來取得想要的值.)
Set 和 List 對比:
Set:檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變.
List:和數組類似,List 可以動態增長,查找元素效率高,插入刪除元素效率低,因爲會引起其他元素位置改變

1.3 List和Map區別

List 是對象集合,允許對象重複
Map 是鍵值對的集合,不允許 key 重複

1.4 ArrayList與Vector區別

ArrayList 和Vector 都是用變長數組實現的,主要有這麼三個區別:
Vector是多線程安全的,線程安全就是說多線程訪問同一代碼,不會產生不確定的結果。而 ArrayList 不是,這個可以從源碼中看出,Vector 類中的方法很多有 synchronized 進行修飾,這樣就導致了 Vector 在效率上無法與 ArrayList 相比;兩個都是採用的線性連續空間存儲元素,但是當空間不足的時候,兩個類的
增加方式是不同。
Vector 可以設置增長因子,而 ArrayList 不可以。
Vector 是一種老的動態數組,是線程同步的,效率很低,一般不贊成使用.

適用場景分析:

Vector 是線程同步的,所以它也是線程安全的,而 ArrayList 是線程異步的, 是不安全的。如果不考慮到線程的安全因素,一般用 ArrayList 效率比較高。
如果集合中的元素的數目大於目前集合數組的長度時,在集合中使用數據量比較大的數據,用 Vector 有一定的優勢

1.5 ArrayList與LinkedList區別

Arraylist:
優點:ArrayList 是實現了基於動態數組的數據結構,因爲地址連續,一旦數據存儲好了,查詢操作效率會比較高(在內存裏是連着放的)
缺點:因爲地址連續, ArrayList 要移動數據,所以插入和刪除操作效率比較低

LinkedList:
優點:LinkedList 基於鏈表的數據結構,地址是任意的,所以在開闢內存空間的時候不需要等一個連續的地址,對於新增和刪除操作 add 和 remove,LinedList 比較佔優勢。LinkedList 適用於要頭尾操作或插入指定位置的場景
缺點:因爲 LinkedList 要移動指針,所以查詢操作性能比較低.
適用場景分析:
當需要對數據進行對此訪問的情況下選用 ArrayList,當需要對數據進行多次增加刪除修改時採用 LinkedList.

1.6 HashMap和HashTable的區別

  1. hashMap 去掉了 HashTable 的 contains 方法,但是加上了 containsValue
    ()和 containsKey()方法。
  2. hashTable 同步的,而 HashMap 是非同步的,效率上逼 hashTable 要高.
  3. hashMap 允許空鍵值,而 hashTable 不允許。
    注意:
    TreeMap:非線程安全基於紅黑樹實現。TreeMap 沒有調優選項,因爲該樹總處於平衡狀態。
    Treemap:適用於按自然順序或自定義順序遍歷鍵(key)。

1.7 HashSet和HashMap區別

set 是線性結構,set 中的值不能重複,hashset 是 set 的 hash 實現,hashset中值不能重複是用 hashmap 的 key 來實現的。
map 是鍵值對映射,可以空鍵空值。HashMap 是 Map 接口的 hash 實現,key 的唯一性是通過 key 值 hash 值的唯一來確定,value 值是則是鏈表結構。
他們的共同點都是 hash 算法實現的唯一性,他們都不能持有基本類型,只能持有對象

多線程篇(簡單總結了一下)

1.1 創建線程的方式及實現

Java 中創建線程主要有三種方式:

一、繼承 Thread 類創建線程類
  • 定義 Thread 類的子類,並重寫該類的 run 方法,該 run 方法的方法體就代表了線程要完成的任務。因此把 run()方法稱爲執行體。
  • 創建 Thread 子類的實例,即創建了線程對象。
  • 調用線程對象的 start()方法來啓動該線程。
二、通過 Runnable 接口創建線程類

(1) 定義 runnable 接口的實現類,並重寫該接口的 run()方法,該 run()方法的方法體同樣是該線程的線程執行體
(2) 創建 Runnable 實現類的實例,並依此實例作爲 Thread 的 target 來創建 Thread 對象,該 Thread 對象纔是真正的線程對象.
(3) 調用線程對象的 start()方法來啓動該線程.

三、通過 Callable 和 Future 創建線程

(1) 創建 Callable 接口的實現類,並實現 call()方法,該 call()方法將作爲線程執行體,並且有返回值.
(2) 創建 Callable 實現類的實例,使用 FutureTask 類來包裝 Callable 對象, 該 FutureTask 對象封裝了該 Callable 對象的 call()方法的返回值.
(3) 使用 FutureTask 對象作爲 Thread 對象的 target 創建並啓動新線程.
(4) 調用 FutureTask 對象的 get()方法來獲得子線程執行結束後的返回值

創建線程的三種方式的對比

採用實現 Runnable、Callable 接口的方式創建多線程時,優勢是:線程類只是實現了 Runnable 接口或 Callable 接口,還可以繼承其他類.
在這種方式下,多個線程可以共享同一個 target 對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想.
劣勢是:編程稍微複雜,如果要訪問當前線程,則必須使用Thread.currentThread()方法.
使用繼承 Thread 類的方式創建多線程時優勢是:編寫簡單,如果需要訪問當前線程,則無需使用 Thread.currentThread()方法,直接使用 this 即可獲得當前線程.
劣勢是:線程類已經繼承了 Thread 類,所以不能再繼承其他父類

線程池創建的幾種方式

newFixedThreadPool(int nThreads)
創建一個固定長度的線程池,每當提交一個任務就創建一個線程,直到達到線程池的最大數量,這時線程規模將不再變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程
newCachedThreadPool()

創建一個可緩存的線程池,如果線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增加時,則可以自動添加新線程,線程池的規模不存在任何限制
newSingleThreadExecutor()
這是一個單線程的 Executor,它創建單個工作線程來執行任務,如果這個線程異常結束,會創建一個新的來替代它;它的特點是能確保依照任務在隊列中的順序來串行執行
newScheduledThreadPool(int corePoolSize)
創建了一個固定長度的線程池,而且以延遲或定時的方式來執行任務,類似於 Timer。

線程的生命週期

生命週期的五種狀態

新建(new Thread)
當創建 Thread 類的一個實例(對象)時,此線程進入新建狀態(未被啓動)。例如:Thread t1=new Thread();
就緒(runnable)
線程已經被啓動,正在等待被分配給 CPU 時間片,也就是說此時線程正在就緒隊列中排隊等候得到 CPU 資源。例如:t1.start();
運行(running)
線程獲得CPU 資源正在執行任務(run()方法),此時除非此線程自動放棄 CPU
資源或者有優先級更高的線程進入,線程將一直運行到結束。
死亡(dead)
當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這時線程不可能再進入就緒狀態等待執行.
自然終止:正常運行 run()方法後終止
異常終止:調用 stop()方法讓一個線程終止運行
堵塞(blocked)
由於某種原因導致正在運行的線程讓出 CPU 並暫停自己的執行,即進入堵塞狀態。
正在睡眠:用sleep(long t) 方法可使線程進入睡眠方式。一個睡眠着的線程在指定的時間過去可進入就緒狀態.
正在等待
調用 wait()方法。(調用 motify()方法回到就緒狀態)
被另一個線程所阻塞:調用 suspend()方法。(調用 resume()方法恢復)

悲觀鎖 樂觀鎖

樂觀鎖 悲觀鎖是一種思想。可以用在很多方面.
比如數據庫方面.
悲觀鎖就是 for update(鎖定查詢的行)
樂觀鎖就是 version 字段(比較跟上一次的版本號,如果一樣則更新,如果失敗則要重複讀-比較-寫的操作。)
JDK 方面
悲觀鎖就是 sync
樂觀鎖就是原子類(內部使用 CAS 實現)

本質來說,就是悲觀鎖認爲總會有人搶我的。樂觀鎖就認爲,基本沒人搶。

樂觀鎖的業務場景及實現方式

樂觀鎖(Optimistic Lock):
每次獲取數據的時候,都不會擔心數據被修改,所以每次獲取數據的時候都不會進行加鎖,但是在更新數據的時候需要判斷該數據是否被別人修改過。如果數據被其他線程修改,則不進行數據更新,如果數據沒有被其他線程修改,則進行數據更新。由於數據沒有進行加鎖,期間該數據可以被其他線程進行讀寫操作。

樂觀鎖:比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,數據發生衝突的可能性就會增大,爲了保證數據的一致性,應用層需要不斷的重新獲取數據,這樣會增加大量的查詢操作,降低了系統的吞吐量。

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