Java經(jīng)典面試題匯總:多線程
1. 并行和并發(fā)有什么區(qū)別?
- 并行:多個(gè)處理器或多核處理器同時(shí)處理多個(gè)任務(wù)。
- 并發(fā):多個(gè)任務(wù)在同一個(gè) CPU 核上,按細(xì)分的時(shí)間片輪流(交替)執(zhí)行,從邏輯上來看那些任務(wù)是同時(shí)執(zhí)行。
- 并發(fā) = 兩個(gè)隊(duì)列和一臺(tái)咖啡機(jī)。 并行 = 兩個(gè)隊(duì)列和兩臺(tái)咖啡機(jī)。
2. 線程和進(jìn)程的區(qū)別?
一個(gè)程序下至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程下至少有一個(gè)線程,一個(gè)進(jìn)程下也可以有多個(gè)線程來增加程序的執(zhí)行速度。
3. 守護(hù)線程是什么?
守護(hù)線程是運(yùn)行在后臺(tái)的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。 在 Java 中垃圾回收線程就是特殊的守護(hù)線程
4. 實(shí)現(xiàn)多線程的方式有哪些?
- 繼承Thread類:Java單繼承,不推薦;
- 實(shí)現(xiàn)Runnable接口:Thread類也是繼承Runnable接口,推薦;
- 實(shí)現(xiàn)Callable接口:實(shí)現(xiàn)Callable接口,配合FutureTask使用,有返回值;
- 使用線程池:復(fù)用,節(jié)約資源;
5. 說一下 runnable 和 callable 有什么區(qū)別?
runnable 沒有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的補(bǔ)充
6. sleep() 和 wait() 有什么區(qū)別?
- 兩者都可以暫停線程的執(zhí)行。
- 類的不同:sleep() 來自 Thread,wait() 來自 Object。
- 釋放鎖:sleep() 不釋放鎖;wait() 釋放鎖。
- 用法不同:sleep() 時(shí)間到會(huì)自動(dòng)恢復(fù);wait() 可以使用 notify()/notifyAll()直接喚醒。
7. 線程有哪些狀態(tài)?
- NEW 尚未啟動(dòng)
- RUNNABLE 正在執(zhí)行中
- BLOCKED 阻塞的(被同步鎖或者IO鎖阻塞)
- WAITING 永久等待狀態(tài)
- TIMED_WAITING 等待指定的時(shí)間重新被喚醒的狀態(tài)
- TERMINATED 執(zhí)行完成
8. notify()和 notifyAll()有什么區(qū)別?
notifyAll()會(huì)喚醒所有的線程,notify()之后喚醒一個(gè)線程。notifyAll() 調(diào)用后,會(huì)將全部線程由等待池移到鎖池,然后參與鎖的競(jìng)爭,競(jìng)爭成功則繼續(xù)執(zhí)行,如果不成功則留在鎖池等待鎖被釋放后再次參與競(jìng)爭。 而 notify()只會(huì)喚醒一個(gè)線程,具體喚醒哪一個(gè)線程由虛擬機(jī)控制。
9. 線程的 run() 和 start() 有什么區(qū)別?
start() 方法用于啟動(dòng)線程,run() 方法用于執(zhí)行線程的運(yùn)行時(shí)代碼。 run() 可以重復(fù)調(diào)用,而 start() 只能調(diào)用一次。
10. 創(chuàng)建線程池有哪幾種方式?
線程池創(chuàng)建有七種方式,最核心的是最后一種:
- newSingleThreadExecutor():它的特點(diǎn)在于工作線程數(shù)目被限制為 1,操作一個(gè)無界的工作隊(duì)列, 所以它保證了所有任務(wù)的都是被順序執(zhí)行,最多會(huì)有一個(gè)任務(wù)處于活動(dòng)狀態(tài),并且不允許使用者改動(dòng)線程池實(shí)例,因此可以避免其改變線程數(shù)目;
- newCachedThreadPool():它是一種用來處理大量短時(shí)間工作任務(wù)的線程池,具有幾個(gè)鮮明特點(diǎn):它會(huì)試圖緩存線程并重用, 當(dāng)無緩存線程可用時(shí),就會(huì)創(chuàng)建新的工作線程;如果線程閑置的時(shí)間超過 60 秒,則被終止并移出緩存;長時(shí)間閑置時(shí),這種線程池,不會(huì)消耗什么資源。其內(nèi)部使用 SynchronousQueue 作為工作隊(duì)列;
- newFixedThreadPool(int nThreads):重用指定數(shù)目(nThreads)的線程,其背后使用的是無界的工作隊(duì)列,任何時(shí)候最多有 nThreads 個(gè)工作線程是活動(dòng)的。 這意味著,如果任務(wù)數(shù)量超過了活動(dòng)隊(duì)列數(shù)目,將在工作隊(duì)列中等待空閑線程出現(xiàn);如果有工作線程退出,將會(huì)有新的工作線程被創(chuàng)建,以補(bǔ)足指定的數(shù)目 nThreads;
- newSingleThreadScheduledExecutor():創(chuàng)建單線程池,返回 ScheduledExecutorService,可以進(jìn)行定時(shí)或周期性的工作調(diào)度;
- newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()類似, 創(chuàng)建的是個(gè) ScheduledExecutorService,可以進(jìn)行定時(shí)或周期性的工作調(diào)度,區(qū)別在于單一工作線程還是多個(gè)工作線程;
- newWorkStealingPool(int parallelism):這是一個(gè)經(jīng)常被人忽略的線程池,Java 8 才加入這個(gè)創(chuàng)建方法, 其內(nèi)部會(huì)構(gòu)建ForkJoinPool,利用Work-Stealing算法,并行地處理任務(wù),不保證處理順序;
- ThreadPoolExecutor():是最原始的線程池創(chuàng)建,上面1-3創(chuàng)建方式都是對(duì)ThreadPoolExecutor的封裝
11. 線程池中 submit() 和 execute() 方法有什么區(qū)別?
execute():只能執(zhí)行 Runnable 類型的任務(wù),無返回值
submit():可以執(zhí)行 Runnable 和 Callable 類型的任務(wù),有返回值
12. Java 程序中怎么保證多線程的運(yùn)行安全?
方法一:使用安全類,比如 Java. util. concurrent 下的類。
方法二:使用自動(dòng)鎖 synchronized。
方法三:使用手動(dòng)鎖 Lock。
手動(dòng)鎖 Java 示例代碼如下: Lock lock = new ReentrantLock(); lock. lock(); try { System. out. println("獲得鎖"); } catch (Exception e) { // TODO: handle exception } finally { System. out. println("釋放鎖"); lock. unlock(); }
13. 多線程中 synchronized 鎖升級(jí)的原理是什么?
ynchronized 鎖升級(jí)原理:在鎖對(duì)象的對(duì)象頭里面有一個(gè) threadid 字段,在第一次訪問的時(shí)候 threadid 為空,jvm 讓其持有偏向鎖,并將 threadid 設(shè)置為其線程 id, 再次進(jìn)入的時(shí)候會(huì)先判斷 threadid 是否與其線程 id 一致,如果一致則可以直接使用此對(duì)象,如果不一致,則升級(jí)偏向鎖為輕量級(jí)鎖,通過自旋循環(huán)一定次數(shù)來獲取鎖,執(zhí)行一定次數(shù)之后, 如果還沒有正常獲取到要使用的對(duì)象,此時(shí)就會(huì)把鎖從輕量級(jí)升級(jí)為重量級(jí)鎖,此過程就構(gòu)成了 synchronized 鎖的升級(jí)。 鎖的升級(jí)的目的:鎖升級(jí)是為了減低了鎖帶來的性能消耗。在 Java 6 之后優(yōu)化 synchronized 的實(shí)現(xiàn)方式,使用了偏向鎖升級(jí)為輕量級(jí)鎖再升級(jí)到重量級(jí)鎖的方式,從而減低了鎖帶來的性能消耗
14. 什么是死鎖?
當(dāng)線程 A 持有獨(dú)占鎖a,并嘗試去獲取獨(dú)占鎖 b 的同時(shí),線程 B 持有獨(dú)占鎖 b,并嘗試獲取獨(dú)占鎖 a 的情況下, 就會(huì)發(fā)生 AB 兩個(gè)線程由于互相持有對(duì)方需要的鎖,而發(fā)生的阻塞現(xiàn)象,我們稱為死鎖。
15. 死鎖的必要條件?怎么防止死鎖?
- 互斥:一次只有一個(gè)進(jìn)程可以使用一個(gè)資源。其他進(jìn)程不能訪問已分配給其他進(jìn)程的資源。
- 占有且等待:當(dāng)一個(gè)進(jìn)程在等待分配得到其他資源時(shí),其繼續(xù)占有已分配得到的資源。
- 非搶占:不能強(qiáng)行搶占進(jìn)程中已占有的資源。
- 循環(huán)等待:存在一個(gè)封閉的進(jìn)程鏈,使得每個(gè)資源至少占有此鏈中下一個(gè)進(jìn)程所需要的一個(gè)資源。
防止:
- 死鎖檢測(cè)
- 加鎖順序
- 盡量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),設(shè)置超時(shí)時(shí)間,超時(shí)可以退出防止死鎖。
- 盡量使用 Java. util. concurrent 并發(fā)類代替自己手寫鎖。
- 盡量降低鎖的使用粒度,盡量不要幾個(gè)功能用同一把鎖。 盡量減少同步的代碼塊。
16. ThreadLocal 是什么?有哪些使用場(chǎng)景?
ThreadLocal,即線程本地變量。如果你創(chuàng)建了一個(gè)ThreadLocal變量,那么訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的一個(gè)本地拷貝,多個(gè)線程操作這個(gè)變量的時(shí)候,實(shí)際是操作自己本地內(nèi)存里面的變量,從而起到線程隔離的作用,避免了線程安全問題。常見的ThreadLocal使用場(chǎng)景為用來解決數(shù)據(jù)庫連接、Session管理等。
17. 說一下 synchronized 底層實(shí)現(xiàn)原理?
synchronized 是由一對(duì) monitorenter/monitorexit 指令實(shí)現(xiàn)的,monitor 對(duì)象是同步的基本實(shí)現(xiàn)單元。 在 Java 6 之前,monitor 的實(shí)現(xiàn)完全是依靠操作系統(tǒng)內(nèi)部的互斥鎖,因?yàn)樾枰M(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換,所以同步操作是一個(gè)無差別的重量級(jí)操作,性能也很低。 但在 Java 6 的時(shí)候,Java 虛擬機(jī) 對(duì)此進(jìn)行了大刀闊斧地改進(jìn),提供了三種不同的 monitor 實(shí)現(xiàn), 也就是常說的三種不同的鎖:偏向鎖(Biased Locking)、輕量級(jí)鎖和重量級(jí)鎖,大大改進(jìn)了其性能。
18. synchronized 和 volatile 的區(qū)別是什么?
- volatile 是變量修飾符;synchronized 是修飾類、方法、代碼段。
- volatile 僅能實(shí)現(xiàn)變量的修改可見性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性。
- volatile 不會(huì)造成線程的阻塞;synchronized 可能會(huì)造成線程的阻塞。
19. synchronized 和 Lock 有什么區(qū)別?
- synchronized 可以給類、方法、代碼塊加鎖;而 lock 只能給代碼塊加鎖。
- synchronized 不需要手動(dòng)獲取鎖和釋放鎖,使用簡單,發(fā)生異常會(huì)自動(dòng)釋放鎖,不會(huì)造成死鎖;而 lock 需要自己加鎖和釋放鎖,如果使用不當(dāng)沒有 unLock()去釋放鎖就會(huì)造成死鎖。 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
20. 說一下 atomic 的原理?
atomic 主要利用 CAS (Compare And Wwap) 和 volatile 和 native 方法來保證原子操作, 從而避免 synchronized 的高開銷,執(zhí)行效率大為提升。
21. synchronized 和 ReentrantLock 區(qū)別是什么?
synchronized 早期的實(shí)現(xiàn)比較低效,對(duì)比 ReentrantLock,大多數(shù)場(chǎng)景性能都相差較大, 但是在 Java 6 中對(duì) synchronized 進(jìn)行了非常多的改進(jìn)。
主要區(qū)別如下:
- ReentrantLock 使用起來比較靈活,但是必須有釋放鎖的配合動(dòng)作;
- ReentrantLock 必須手動(dòng)獲取與釋放鎖,而 synchronized 不需要手動(dòng)釋放和開啟鎖; ReentrantLock 只適用于代碼塊鎖,而 synchronized 可用于修飾方法、代碼塊等。
22. LinkedBlockingQueue與ArrayBlockingQueue的區(qū)別?
- 隊(duì)列大小有所不同,ArrayBlockingQueue是有界的初始化必須指定大小,而LinkedBlockingQueue可以是有界的也可以是無界的(Integer.MAX_VALUE),(而且不會(huì)初始化就占用一大片內(nèi)存)對(duì)于后者而言,當(dāng)添加速度大于移除速度時(shí),在無界的情況下,可能會(huì)造成內(nèi)存溢出等問題。
- 數(shù)據(jù)存儲(chǔ)容器不同,ArrayBlockingQueue采用的是數(shù)組作為數(shù)據(jù)存儲(chǔ)容器,而LinkedBlockingQueue采用的則是以Node節(jié)點(diǎn)作為連接對(duì)象的鏈表。
- 由于ArrayBlockingQueue采用的是數(shù)組的存儲(chǔ)容器,因此在插入或刪除元素時(shí)不會(huì)產(chǎn)生或銷毀任何額外的對(duì)象實(shí)例,而LinkedBlockingQueue則會(huì)生成一個(gè)額外的Node對(duì)象。這可能在長時(shí)間內(nèi)需要高效并發(fā)地處理大批量數(shù)據(jù)的時(shí),對(duì)于GC可能存在較大影響。
- 兩者的實(shí)現(xiàn)隊(duì)列添加或移除的鎖不一樣,ArrayBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是沒有分離的,即添加操作和移除操作采用的同一個(gè)ReenterLock鎖,而LinkedBlockingQueue實(shí)現(xiàn)的隊(duì)列中的鎖是分離的,其添加采用的是putLock,移除采用的則是takeLock,這樣能大大提高隊(duì)列的吞吐量,也意味著在高并發(fā)的情況下生產(chǎn)者和消費(fèi)者可以并行地操作隊(duì)列中的數(shù)據(jù),以此來提高整個(gè)隊(duì)列的并發(fā)性能。
- 兩者的size都是強(qiáng)一致的。但是實(shí)現(xiàn)有區(qū)別,Array~使用全局鎖 Linked~使用原子變量實(shí)現(xiàn)。
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot 文件或圖片上傳與下載功能的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot 文件或圖片上傳與下載功能的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02Java數(shù)據(jù)結(jié)構(gòu)中關(guān)于AVL樹的實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中關(guān)于AVL樹的實(shí)現(xiàn)方法,AVL樹是高度平衡的二叉樹,它的特點(diǎn)是AVL樹中任何節(jié)點(diǎn)的兩個(gè)子樹的高度最大差別為1,本文主要給大家介紹了Java語言如何實(shí)現(xiàn)AVL樹,需要的朋友可以參考下2024-02-02Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解
這篇文章主要為大家介紹了Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09

Spring如何基于Proxy及cglib實(shí)現(xiàn)動(dòng)態(tài)代理

關(guān)于fastjson的@JSONField注解的一些問題(詳解)

mybatis plus使用redis作為二級(jí)緩存的方法