1. ConcurrentHashMap的讀是否要加鎖,為什么?
讀操作沒有加鎖,目的是為了進一步降低鎖沖突的概率,為了保證讀到剛修改的數據,搭配了volatile 關鍵字;
2. 介紹下 ConcurrentHashMap 的鎖分段技術?
這個是 Java1.7 中采取的技術,Java1.8 中已經不再使用了,簡單的說就是把若干個哈希桶分成一個 "段"(Segment),針對每個段分別加鎖,目的也是為了降低鎖競爭的概率,當兩個線程訪問的數據恰好在同一個段上的時候,才觸發鎖競爭;
3. ConcurrentHashMap在jdk1.8做了哪些優化?
取消了分段鎖,直接給每個哈希桶(每個鏈表)分配了一個鎖(就是以每個鏈表的頭結點對象作為鎖對 象),將原來 數組 + 鏈表 的實現方式改進成 數組 + 鏈表 / 紅黑樹 的方式,當鏈表較長的時候(大于等于 8 個元素,并且數組長度大于等于 64)就轉換成紅黑樹;
4. Hashtable 和 HashMap、ConcurrentHashMap 之間的區別?
HashMap: 線程不安全,key 允許為 null,
Hashtable: 線程安全,使用 synchronized 鎖 Hashtable 對象,效率較低,key 不允許為 null,
ConcurrentHashMap: 線程安全,使用 synchronized 鎖每個鏈表頭結點,鎖沖突概率低,充分利用 CAS 機制,優化了擴容方式,key 不允許為 null;
5. 談談 volatile 關鍵字的用法?
volatile 能夠保證內存可見性,強制從主內存中讀取數據,此時如果有其他線程修改被 volatile 修飾的變量,可以第?時間讀取到最新的值,volatile 還可以避免指令重排序引起的問題;
6. Java多線程是如何實現數據共享的?
JVM 把內存分成了這幾個區域: 方法區,堆區,棧區,程序計數器,其中方法區和堆區是多個線程之間共享的,只要把某個數據放到堆內存中,就可以讓多個線程都能訪問到;
7. Java 創建線程池的接口是什么?參數的含義?
在這兩篇博客中都有涉及:Java 多線程(01)—— 認識線程和創建線程,線程池拒絕策略創建線程池主要有兩種方式:通過 Executors 工廠類創建,創建方式比較簡單,但是定制能力有限;通過ThreadPoolExecutor 創建.創建方式比較復雜,但是定制能力強;
8. LinkedBlockingQueue 的作用是什么?
LinkedBlockingQueue 表示線程池的任務隊列,用戶通過 submit / execute向這個任務隊列中添加任務,再由線程池中的工作線程來執行任務;
9. Java線程共有幾種狀態?狀態之間怎么切換的?
NEW:安排了工作還未開始行動,新創建的線程,還沒有調用 start 方法時處在這個狀態;
RUNNABLE:可工作的,又可以分成正在工作中和即將開始工作,調用?start 方法之后,并正在CPU上 運行 / 在即將準備運行的狀態
BLOCKED:使用 synchronized 時,如果鎖被其他線程占用,就會阻塞等待,并進入該狀態;
WAITING:調用 wait 方法會進入該狀態;
TIMED_WAITING:調用 sleep 方法或者 wait(超時時間) 會進入該狀態;
TERMINATED:工作完成了,當線程 run 方法執行完畢后,會處于這個狀態;
10. 在多線程下,如果對一個數進行疊加,該怎么做?
使用 synchronized/ReentrantLock 加鎖,使用 AtomicInteger 原子操作;?
11. Servlet是否是線程安全的?
Servlet 本身是工作在多線程環境下,如果在 Servlet 中創建了某個成員變量,此時如果有多個請求到達服務器,服務器就會在多線程進行操作,是可能出現線程不安全的情況的;
12. Thread 和 Runnable 的區別和聯系?
Thread 類描述了一個線程,Runnable描述了一個任務;在創建線程的時候需要指定線程完成的任務,可以直接重寫 Thread 的 run 方法,也可以使用 Runnable 來描述這個任務;
13. 多次 start ?個線程會怎么樣?
第一次調用 start 可以成功,后續再調用 start 會拋出 java.lang.IllegalThreadStateException 異常
14. 有 synchronized 兩個方法,兩個線程分別同時用這個方法,請問會發生什么?
synchronized 加在非靜態方法上,相當于針對當前對象加鎖;
如果這兩個方法屬于同一個實例:線程1 能夠獲取到鎖,并執行方法,線程2 會阻塞等待,直到線程1 執行完畢,釋放鎖,線程2 獲取到鎖之后才能執行方法內容;
如果這兩個方法屬于不同實例:兩者能并發執行,互不干擾;
15. 進程和線程的區別?
進程是包含線程的,每個進程至少有一個線程存在,即主線程;進程和進程之間不共享內存空間.同一個進程的線程之間共享同一個內存空間;進程是系統分配資源的最小單位,線程是系統調度的最小單位。
16. 線程同步的方式有哪些?
synchronized,ReentrantLock,Semaphore 等都可以用于線程同步;
17. 為什么有了 synchronized 還需要 juc 下的 lock?
以 juc 的 ReentrantLock 為例,synchronized 使用時不需要手動釋放鎖,ReentrantLock 使用時需要手動釋放,使用起來更靈活;synchronized 在申請鎖失敗時,會死等,ReentrantLock 可以通過trylock 的方式等待一段時間就放棄;synchronized 是非公平鎖,ReentrantLock 默認是非公平鎖,但可以通過構造方法傳入一個 true 開啟公平鎖模式;synchronized 是通過 Object 的 wait/notify 實現等待,喚醒,每次喚醒的是一個隨機等待的線程,ReentrantLock 搭配 Condition 類實現等待喚醒,可以更精確控制喚醒某個指定的線程;
18. AtomicInteger 的實現原理是什么?
基于 CAS 機制,詳細信息可以看這篇博客:Java多線程(04)
19. 信號量聽說過么?之前都用在過哪些場景下?
信號量,用來表示 "可用資源的個數",本質上就是一個計數器,使用信號量可以實現 "共享鎖" ,比如某個資源允許 3 個線程同時使用,那么就可以使用?P 操作作為加鎖,V操作作為解鎖,前三個線程的P操作都能順利返回,后續線程再進行 P 操作就會阻塞等待,直到前面的線程執行了 V 操作;
20. 什么是偏向鎖?
偏向鎖不是真的加鎖,而只是在鎖的對象頭中記錄一個標記(記錄該鎖所屬的線程),如果沒有其他線程參與競爭鎖,那么就不會真正執行加鎖操作,從而降低程序開銷,一旦真的涉及到其他的線程競爭,再取消偏向鎖狀態,進入輕量級鎖狀態;
21. 講解下你自己理解的 CAS 機制
全稱Compareandswap,即"比較并交換",相當于通過一個原?的操作,同時完成 "讀取內存,比較是否相等,修改內存" 這三個步驟,本質上需要 CPU 指令的支撐;
22. ABA 問題怎么解決?
給要修改的數據引入版本號;在 CAS 比較數據當前值和舊值的同時,也要比較版本號是否符合預期,如果發現當前版本號和之前讀到的版本號一致,就真正執行修改操作,并讓版本號自增,如果發現當前版本號比之前讀到的版本號大,就認為操作失敗;
23. 你是怎么理解樂觀鎖和悲觀鎖的,具體怎么實現呢?
悲觀鎖認為多個線程訪問同一個共享變量沖突的概率較大,會在每次訪問共享變量之前都去真正加鎖.;樂觀鎖認為多個線程訪問同一個共享變量沖突的概率不大,并不會真的加鎖,而是直接嘗試訪問數據.,在訪問的同時識別當前的數據是否出現訪問沖突;悲觀鎖的實現就是先加鎖(比如借助操作系統提供的 mutex),獲取到鎖再操作數據,獲取不到鎖就等待,樂觀鎖的實現可以引入一個版本號;借助版本號識別出當前的數據訪問是否沖突;
24. 介紹下讀寫鎖
讀寫鎖就是把讀操作和寫操作分別進行加鎖,讀鎖和讀鎖之間不互斥,寫鎖和寫鎖之間互斥,寫鎖和讀鎖之間互斥,讀寫鎖最主要用在 "頻繁讀,不頻繁寫" 的場景中;
25. 什么是自旋鎖,為什么要使用自旋鎖策略呢,缺點是什么?
如果獲取鎖失敗,立即再嘗試獲取鎖,無限循環,直到獲取到鎖為止,第一次獲取鎖失敗,第二次的嘗試會在極短的時間內到來,一旦鎖被其他線程釋放,就能第一時間獲取到鎖,
相比于掛起等待鎖,優點:沒有放棄CPU資源,一旦鎖被釋放就能第一時間獲取到鎖,更高效,在鎖持有時間比較短的場景下非常有用;缺點:如果鎖的持有時間較長,就會浪費 CPU 資源;
26. synchronized 是可重入鎖么?
是可重入鎖,可重入鎖指的就是連續兩次加鎖不會導致死鎖,實現的方式是在鎖中記錄該鎖持有的線程身份,以及?個計數器(記錄加鎖次數),如果發現當前加鎖的線程就是持有鎖的線程,則直接計數自增;