多线程常见问题总结
多線程常見問題總結
目錄
1. 并行和并發有什么區別?
- 并行:多個處理器或多核處理器同時處理多個任務。
- 并發:多個任務在同一個cpu核上,按細分的時間片輪流執行,從邏輯上看那些任務是同時執行。
并發:兩個隊列和一臺咖啡機。
并行:兩個隊列和兩臺咖啡機。
2. 線程和進程的區別?
3. 守護線程是什么?
守護線程是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。在 java 中垃圾收回線程就是特殊的守護線程。
4. 創建線程有幾種方式?
創建線程有三種方式:
- 繼承Thread 重寫 run 方法。
- 實現 Runnable 接口。
- 實現 Callable 接口。
5. 說一下 rannable 和 callable 有什么區別?
runnable 沒有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的補充。
6. 線程有哪些狀態?
線程的狀態:
- NEW:尚未啟動
- RUNNABLE:正在執行
- BLOCKED:阻塞的(被同步鎖或IO鎖阻塞)
- WAITING:永久等待狀態
- TIMED_WAITING:等待指定的時間重新被喚醒的狀態
- TERMINATED:執行完成
7. sleep() 和 wait() 有什么區別?
- 類的不同:sleep() 來自 Thread,wait() 來自 Object。
- 釋放鎖:sleep() 不釋放鎖;wait() 釋放鎖。
- 用法不同:sleep() 時間到會自動恢復;wait() 可以使用 notify/ notifyAll 直接喚醒。
8. notify() 和 notifyAll 有什么區別?
9. 線程的 run() 和 start() 有什么區別?
start() 方法用于啟動線程,run() 方法用于執行線程的運行時代碼。run() 可以重復調用,start()方法只能調用一次。
10. 創建線程池有哪幾種方式?
線程池創建有七種方式,最核心的是最后一種:
- newSingleThreadExecutor():它的特點在于工作線程數目被限制為1,操作一個無界的工作隊列,所以它保證了所有任務都是被順序執行,最多會有一個任務處于活動狀態,并且不允許使用者改動線程池實例,因此可以避免其改變線程數量。
- newCachedThreadPool():它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明的特點:它會試圖緩存線程并重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閑置的時間超過60s,則被終止并移出緩存;長時間閑置的,這種線程池,不會消耗什么資源。其內部使用SynchronousQueue作為工作隊列;
- newFixedThreadPool(int nTreands):重用指定數目的線程,其背后使用的是無界的工作隊列,任何時候最多有 nThread 個工作線程是活動的。這意味著,如果任務數量超過了活動隊列數目,將在工作隊列中等待空閑線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目 nThread;
- newSingleThreadScheduleExecutor():創建單線程池,返回ScheduledExecutorService,可以進行定時或周期性的工作調度;
- newSchduleThreadPool(int corePoolSize):和newSingleThreadScheduleExecutor() 類似,創建的是個 ScheduledExecutorService,可以進行定時或周期性的工作調度,區別在于單一工作線程還是多個工作線程;
- newWorkStealingPool(int parallelism):這是一個經常被人忽略的線程池,Java 8 才加入這個創建方法,其內部會構建 ForkJoinPool,利用 Work-Stealing 算法,并行地處理任務,不保證處理順序。
- TreadPoolExecutor():是最原始的線程池創建,上面1-3創建方法都是對TreadPoolExecutor的封裝。
11. 線程池都有哪些狀態?
- RUNNING:這是最正常的狀態,接受新的任務,處理等待隊列中的任務。
- SHUTDOWN:不接受新的任務提交,但是會繼續處理等待隊列中的任務。
- STOP:不接受新的任務提交,不再處理等待隊列中的任務,中斷正在執行的線程。
- TIDYING:所有的任務都銷毀了,workCount為0,線程池的狀態再轉換為TIDYING狀態時,會執行鉤子方法 terminated()。
- TERMINATED:terminated() 方法結束后,線程池的狀態就會變成這個。
12. 線程池中 submit() 和 execute() 方法有什么區別?
- execute():只能執行 Runnable 類型的任務。
- submit():可以執行 Runnable 和 Callable 類型的任務。
Callable 類型的任務可以獲取執行的返回值,而 Runnable 執行無返回值。
13. 在 java 程序中怎么保證多線程的運行安全?
- 方法一:使用安全類,比如 Java.util.concurrent 下的類。
- 方法二:使用自動鎖 synchronized。
- 方法三:使用手動鎖 Lock。
14. 多線程鎖的升級原理是什么?
synchronized 鎖升級原理:在鎖對象的對象頭里面有一個 threadid 字段,在第一次訪問的時候 threadid 為空,jvm讓其持有偏向鎖,并將 threadid 設置為其線程 id,再次進入的時候會先判斷 threadid 是否與其線程 id 一致,如果一致則可以直接使用此對象,如果不一致,則升級偏向鎖為輕量級鎖,通過自旋循環一定次數來獲取鎖,執行一定次數之后,如果還沒有正常獲取到要使用的對象,此時就把鎖從輕量級升級為重量級鎖,此過程就構成了 synchronized 鎖的升級。
鎖的升級目的:鎖升級是為了減低鎖帶來的性能消耗。在 java 6 之后優化 synchronized 的實現方法,使用了偏向鎖升級為輕量級鎖再升級到重量級鎖的方法,從而降低了鎖帶來的性能消耗。
15. 什么是死鎖?
當線程 A 持有獨占鎖a,并嘗試去獲取獨占鎖 b 的同時,線程 B 持有獨占鎖 b,并嘗試獲取獨占鎖 a 的情況下,就會發生 AB 兩個線程由于相互持有對方需要的鎖,而發生阻塞現象,我們稱為死鎖。
16. 怎么防止死鎖?
- 盡量使用 tryLock(long timeout, TimeUnit unit) 的方法(ReentrantLock、ReentrantReadWriteLock),設置超時時間,超時可以退出防止死鎖。
- 盡量使用 Java.util.concurrent 并發類代替自己手寫鎖。
- 盡量降低鎖的粒度,盡量不要幾個功能用同一把鎖。
- 盡量減少同步的代碼塊。
17. TreadLocal 是什么?有哪些使用場景?
ThreadLocal 為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。
ThreadLocal 的經典使用場景是數據庫連接和session 管理等。
18. 說一下 synchronized 底層實現原理?
synchronized 是由一對 monitorenter/monitorexit 指令實現的,monitor 對象是同步的基本單元。在Java 6 之前,monitor 的實現完全依靠操作系統內部的互斥鎖,因此需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作,性能也很低。但在 java 6 的時候,Java 虛擬機對此進行了改進,提供了三種不同的 monitor 實現,也就是常說的三種不同的鎖:偏向鎖(Biased Locking)、輕量級鎖和重量級鎖,大大改進其性能。
- 偏向鎖:無實際競爭,且將來只有第一個申請鎖的線程會使用鎖。
- 輕量級鎖:無實際競爭,多個線程交替使用鎖;允許短時間的鎖競爭。
- 重量級鎖:有實際競爭,且鎖競爭時間長。
19. synchronized 和 volatile 的區別是什么?
- volatile 是變量修飾符;synchronized是修飾類,方法,代碼塊。
- volatile 僅能實現變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性。
- volatile 不會造成線程阻塞;synchronized 可能會造成線程的阻塞。
20. synchronized 和 Lock 有什么區別?
- synchronized 可以給類、方法、代碼塊加鎖;而 lock 只能給代碼塊加鎖。
- synchronized 不需要手動獲取鎖和釋放鎖,使用簡單,發送異常會自動釋放鎖,不會造成死鎖;而 lock 需要自己加鎖和釋放鎖,如果使用不當沒有 unLock() 去釋放鎖就會造成死鎖。
- 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 無法辦到。
21. synchronized 和 ReentrantLock 區別是什么?
synchronized 早期實現比較低效,對比 ReentrantLock,大多數場景性能都相差較大,但是在 java 6 中對synchronized 進行了非常多的改進。
主要區別如下:
Lock 相比較 synchronized 的優點
22. 說一下 atomic 的原理?
atomic 主要利用 CAS(Compare And Swap)和 volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大為提高。
總結
- 上一篇: 反射、对象拷贝、异常常见问题总结
- 下一篇: 数据结构与算法之堆排序