面试:史上最全多线程面试题 - (锁内存模型线程)
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/xxyybs/article/details/102793030
多線程經典面試題60問。
歷史文章:
dubbo&zookeeper55道高頻面試題(附加答案)
SpringCloud&SpringBoot經典面試題(附加答案)
1.什么是活鎖、饑餓、無鎖、死鎖?
死鎖、活鎖、饑餓是關于多線程是否活躍出現的運行阻塞障礙問題,如果線程出現 了這三種情況,即線程不再活躍,不能再正常地執行下去了。
死鎖
死鎖是多線程中最差的一種情況,多個線程相互占用對方的資源的鎖,而又相互等 對方釋放鎖,此時若無外力干預,這些線程則一直處理阻塞的假死狀態,形成死鎖。 舉個例子,A 同學搶了 B 同學的鋼筆,B 同學搶了 A 同學的書,兩個人都相互占 用對方的東西,都在讓對方先還給自己自己再還,這樣一直爭執下去等待對方還而 又得不到解決,老師知道此事后就讓他們相互還給對方,這樣在外力的干預下他們 才解決,當然這只是個例子沒有老師他們也能很好解決,計算機不像人如果發現這 種情況沒有外力干預還是會一直阻塞下去的。
活鎖
活鎖這個概念大家應該很少有人聽說或理解它的概念,而在多線程中這確實存在。 活鎖恰恰與死鎖相反,死鎖是大家都拿不到資源都占用著對方的資源,而活鎖是拿 到資源卻又相互釋放不執行。當多線程中出現了相互謙讓,都主動將資源釋放給別 的線程使用,這樣這個資源在多個線程之間跳動而又得不到執行,這就是活鎖。
饑餓
我們知道多線程執行中有線程優先級這個東西,優先級高的線程能夠插隊并優先執 行,這樣如果優先級高的線程一直搶占優先級低線程的資源,導致低優先級線程無 法得到執行,這就是饑餓。當然還有一種饑餓的情況,一個線程一直占著一個資源 不放而導致其他線程得不到執行,與死鎖不同的是饑餓在以后一段時間內還是能夠 得到執行的,如那個占用資源的線程結束了并釋放了資源。
無鎖
無鎖,即沒有對資源進行鎖定,即所有的線程都能訪問并修改同一個資源,但同時 只有一個線程能修改成功。無鎖典型的特點就是一個修改操作在一個循環內進行, 線程會不斷的嘗試修改共享資源,如果沒有沖突就修改成功并退出否則就會繼續下 一次循環嘗試。所以,如果有多個線程修改同一個值必定會有一個線程能修改成功, 而其他修改失敗的線程會不斷重試直到修改成功。之前的文章我介紹過 JDK 的 CAS 原理及應用即是無鎖的實現。 可以看出,無鎖是一種非常良好的設計,它不會出現線程出現的跳躍性問題,鎖使 用不當肯定會出現系統性能問題,雖然無鎖無法全面代替有鎖,但無鎖在某些場合 下是非常高效的。
2.線程和進程的區別是什么?
進程和線程的主要差別在于它們是不同的操作系統資源管理方式。進程有獨立的地 址空間,一個進程崩潰后,在保護模式下不會對其它進程產生影響,而線程只是一 個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的 地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發操作,只能用線程,不能用進程。
3.Java 實現線程有哪幾種方式?
1)繼承 Thread 類實現多線程
2)實現 Runnable 接口方式實現多線程
3)使用 ExecutorService、Callable、Future 實現有返回結果的多線程
4)通過線程池創建線程
4.啟動線程方法 start()和 run()有什么區別?
只有調用了 start()方法,才會表現出多線程的特性,不同線程的 run()方法里面的代碼交替執行。如果只是調用 run()方法,那么代碼還是同步執行的,必須等待一個線程的 run()方法里面的代碼全部執行完畢之后,另外一個線程才可以執行其 run() 方法里面的代碼。
5.怎么終止一個線程?如何優雅地終止線程?
stop 終止,不推薦。
6.一個線程的生命周期有哪幾種狀態?它們之間如何流轉的?
NEW:毫無疑問表示的是剛創建的線程,還沒有開始啟動。
RUNNABLE: 表示線程已經觸發 start()方式調用,線程正式啟動,線程處于運行中 狀態。
BLOCKED:表示線程阻塞,等待獲取鎖,如碰到 synchronized、lock 等關鍵字等占用臨界區的情況,一旦獲取到鎖就進行 RUNNABLE 狀態繼續運行。
WAITING:表示線程處于無限制等待狀態,等待一個特殊的事件來重新喚醒,如 通過wait()方法進行等待的線程等待一個 notify()或者 notifyAll()方法,通過 join()方 法進行等待的線程等待目標線程運行結束而喚醒,一旦通過相關事件喚醒線程,線 程就進入了 RUNNABLE 狀態繼續運行。
TIMED_WAITING:表示線程進入了一個有時限的等待,如 sleep(3000),等待 3 秒 后線程重新進行 RUNNABLE 狀態繼續運行。
TERMINATED:表示線程執行完畢后,進行終止狀態。需要注意的是,一旦線程通過start 方法啟動后就再也不能回到初始 NEW 狀態,線程終止后也不能再回到 RUNNABLE 狀態 。
7.線程中的 wait()和 sleep()方法有什么區別?
這個問題常問,sleep 方法和 wait 方法都可以用來放棄 CPU 一定的時間,不同點在于如果線程持有某個對象的監視器,sleep 方法不會放棄這個對象的監視器,wait 方法會放棄這個對象的監視器。
8.多線程同步有哪幾種方法?
Synchronized 關鍵字,Lock 鎖實現,分布式鎖等。
9.多線程有什么用?
1)發揮多核CPU的優勢
隨著工業的進步,現在的筆記本、臺式機乃至商用的應用服務器至少也都是雙核的 ,4 核、8 核甚至 16 核的也都不少見,如果是單線程的程序,那么在雙核 CPU 上 就浪費了 50%, 在 4 核 CPU 上就浪費了 75%。單核 CPU 上所謂的"多線程"那是 假的多線程,同一時間處理器只會處理一段邏輯,只不過線程之間切換得比較快, 看著像多個線程"同時"運行罷了。多核 CPU 上的多線程才是真正的多線程,它能 讓你的多段邏輯同時工作,多線程,可以真正發揮出多核CPU 的優勢來,達到充 分利用CPU 的目的。
2)防止阻塞
從程序運行效率的角度來看,單核 CPU 不但不會發揮出多線程的優勢,反而會因 為在單核CPU 上運行多線程導致線程上下文的切換,而降低程序整體的效率。但 是單核 CPU 我們還是要應用多線程,就是為了防止阻塞。試想,如果單核 CPU 使 用單線程,那么只要這個線程阻塞了,比方說遠程讀取某個數據吧,對端遲遲未返 回又沒有設置超時時間,那么你的整個程序在數據返回回來之前就停止運行了。多 線程可以防止這個問題,多條線程同時運行,哪怕一條線程的代碼執行讀取數據阻 塞,也不會影響其它任務的執行。
3)便于建模
這是另外一個沒有這么明顯的優點了。假設有一個大的任務 A,單線程編程,那么 就要考慮很多,建立整個程序模型比較麻煩。但是如果把這個大的任務 A 分解成 幾個小任務,任務B、任務 C、任務 D,分別建立程序模型,并通過多線程分別運 行這幾個任務,那就簡單很多了。
10.多線程之間如何進行通信?
wait/notify
11、線程怎樣拿到返回結果?
實現Callable 接口。
12、violatile 關鍵字的作用?
一個非常重要的問題,是每個學習、應用多線程的 Java 程序員都必須掌握的。理 解 volatile關鍵字的作用的前提是要理解 Java 內存模型,這里就不講 Java 內存模型 了,可以參見第31 點,volatile 關鍵字的作用主要有兩個:
1)多線程主要圍繞可見性和原子性兩個特性而展開,使用 volatile 關鍵字修飾的變 量,保證了其在多線程之間的可見性,即每次讀取到 volatile 變量,一定是最新的 數據
2)代碼底層執行不像我們看到的高級語言----Java 程序這么簡單,它的執行是 Java 代碼–>字節碼–>根據字節碼執行對應的 C/C++代碼–>C/C++代碼被編譯成匯編語 言–>和硬件電路交互,現實中,為了獲取更好的性能 JVM 可能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用 volatile 則會對禁止語義重排 序,當然這也一定程度上降低了代碼執行效率從實踐角度而言,volatile 的一個重 要 作 用 就 是 和 CAS 結 合 , 保證了原子性 ,詳細的可以參見 java.util.concurrent.atomic 包下的類,比如 AtomicInteger。
13、新建 T1、T2、T3 三個線程,如何保證它們按順序執行?
用 join 方法。
14、怎么控制同一時間只有 3 個線程運行?
用 Semaphore。
15、為什么要使用線程池?
我們知道不用線程池的話,每個線程都要通過 new Thread(xxRunnable).start()的方 式來創建并運行一個線程,線程少的話這不會是問題,而真實環境可能會開啟多個線程讓系統和程序達到最佳效率,當線程數達到一定數量就會耗盡系統的 CPU 和 內存資源,也會造成 GC頻繁收集和停頓,因為每次創建和銷毀一個線程都是要消 耗系統資源的,如果為每個任務都創建線程這無疑是一個很大的性能瓶頸。所以, 線程池中的線程復用極大節省了系統資源,當線程一段時間不再有任務處理時它也 會自動銷毀,而不會長駐內存。
16、常用的幾種線程池并講講其中的工作原理。
什么是線程池?
很簡單,簡單看名字就知道是裝有線程的池子,我們可以把要執行的多線程交給線 程池來處理,和連接池的概念一樣,通過維護一定數量的線程池來達到多個線程的復用。
線程池的好處
我們知道不用線程池的話,每個線程都要通過 new Thread(xxRunnable).start()的方 式來創建并運行一個線程,線程少的話這不會是問題,而真實環境可能會開啟多個 線程讓系統和程序達到最佳效率,當線程數達到一定數量就會耗盡系統的 CPU 和 內存資源,也會造成 GC頻繁收集和停頓,因為每次創建和銷毀一個線程都是要消 耗系統資源的,如果為每個任務都創建線程這無疑是一個很大的性能瓶頸。所以, 線程池中的線程復用極大節省了系統資源,當線程一段時間不再有任務處理時它也 會自動銷毀,而不會長駐內存。
線程池核心類
在 java.util.concurrent 包中我們能找到線程池的定義,其中 ThreadPoolExecutor 是 我們線程池核心類,首先看看線程池類的主要參數有哪些。
如何提交線程
如可以先隨便定義一個固定大小的線程池 ExecutorService es = Executors.newFixedThreadPool(3);提交一個線程es.submit(xxRunnble); es.execute(xxRunnble);
如何關閉線程池es.shutdown();
不再接受新的任務,之前提交的任務等執行結束再關閉線程池。 es.shutdownNow();
不再接受新的任務,試圖停止池中的任務再關閉線程池,返回所有未處理的線程 list 列表。
17、線程池啟動線程 submit()和 execute()方法有什么不同?
execute 沒有返回值,如果不需要知道線程的結果就使用 execute 方法,性能會好很 多。 submit 返回一個 Future 對象,如果想知道線程結果就使用 submit 提交,而且它能 在主線程中通過 Future 的 get 方法捕獲線程中的異常。
18、CyclicBarrier 和 CountDownLatch 的區別?
兩個看上去有點像的類,都在 java.util.concurrent 下,都可以用來表示代碼運行到 某個點上,二者的區別在于:
1.CyclicBarrier 的某個線程運行到某個點上之后,該線程即停止運行,直到所有的 線程都到達了這個點,所有線程才重新運行;CountDownLatch 則不是,某線程運 行到某個點上之后,只是給某個數值-1 而已,該線程繼續運行。
2.CyclicBarrier 只能喚起一個任務,CountDownLatch 可以喚起多個任務。
3.CyclicBarrier 可 重 用 , CountDownLatch 不可重用 , 計數值為 0 該CountDownLatch就不可再用了
19.什么是死鎖?如何避免死鎖?
死鎖就是兩個線程相互等待對方釋放對象鎖。
如何避免死鎖:
加鎖順序:線程按照一定的順序加鎖
加鎖時限:線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,并釋放自己占有的鎖
死鎖檢測
20、什么是原子性、可見性、有序性?
原子性、可見性、有序性是多線程編程中最重要的幾個知識點,由于多線程情況復 雜,如何讓每個線程能看到正確的結果,這是非常重要的。
**原子性
** 原子性是指一個線程的操作是不能被其他線程打斷,同一時間只有一個線程對一個 變量進行操作。在多線程情況下,每個線程的執行結果不受其他線程的干擾,比如 說多個線程同時對同一個共享成員變量 n++100 次,如果 n 初始值為 0,n 最后的 值應該是 100,所以說它們是互不干擾的,這就是傳說的中的原子性。但 n++并不 是原子性的操作,要使用 AtomicInteger 保證原子性。
可見性
可見性是指某個線程修改了某一個共享變量的值,而其他線程是否可以看見該共享 變量修改后的值。在單線程中肯定不會有這種問題,單線程讀到的肯定都是最新的值,而在多線程編程中就不一定了。每個線程都有自己的工作內存,線程先把共享 變量的值從主內存讀到工作內存,形成一個副本,當計算完后再把副本的值刷回主 內存,從讀取到最后刷回主內存這是一個過程,當還沒刷回主內存的時候這時候對 其他線程是不可見的,所以其他線程從主內存讀到的值是修改之前的舊值。像 CPU 的緩存優化、硬件優化、指令重排及對 JVM 編譯器的優化,都會出現可見性的問題。
有序性
我們都知道程序是按代碼順序執行的,對于單線程來說確實是如此,但在多線程情 況下就不是如此了。為了優化程序執行和提高 CPU 的處理性能,JVM 和操作系統 都會對指令進行重排,也就說前面的代碼并不一定都會在后面的代碼前面執行,即 后面的代碼可能會插到前面的代碼之前執行,只要不影響當前線程的執行結果。所 以,指令重排只會保證當前線程執行結果一致,但指令重排后勢必會影響多線程的 執行結果。雖然重排序優化了性能,但也是會遵守一些規則的,并不能隨便亂排序, 只是重排序會影響多線程執行的結果。
21、什么是守護線程?有什么用?
什么是守護線程?與守護線程相對應的就是用戶線程,守護線程就是守護用戶線程,當用戶線程全部執行完結束之后,守護線程才會跟著結束。也就是守護線程必 須伴隨著用戶線程,如果一個應用內只存在一個守護線程,沒有用戶線程,守護線 程自然會退出。
22、一個線程運行時發生異常會怎樣?
如果異常沒有被捕獲該線程將會停止執行。Thread.UncaughtExceptionHandler 是用 于處理未捕獲異常造成線程突然中斷情況的一個內嵌接口。當一個未捕獲異常將造 成線程中斷的時 候 JVM 會 使 用 Thread.getUncaughtExceptionHandler() 來 查 詢 線 程 的UncaughtExceptionHandler 并 將 線 程 和 異 常 作 為 參 數 傳 遞 給 handler 的 uncaughtException()方法進行處理。
23、線程 yield()方法有什么用?
Yield 方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。 它是一個靜態方法而且只保證當前線程放棄 CPU 占用而不能保證使其它線程一定 能占用 CPU,執行yield()的線程有可能在進入到暫停狀態后馬上又被執行。
24、什么是重入鎖?
所謂重入鎖,指的是以線程為單位,當一個線程獲取對象鎖之后,這個線程可以再 次獲取本對象上的鎖,而其他的線程是不可以的。
25、Synchronized 有哪幾種用法?
鎖類、鎖方法、鎖代碼塊。
26、Fork/Join 框架是干什么的?
大任務自動分散小任務,并發執行,合并小任務結果。
27、線程數過多會造成什么異常?
線程過多會造成棧溢出,也有可能會造成堆異常。
28、說說線程安全的和不安全的集合。
Java 中平時用的最多的 Map 集合就是 HashMap 了,它是線程不安全的。 看下面兩個場景:
1、當用在方法內的局部變量時,局部變量屬于當前線程級別的變量,其他線程訪 問不了,所以這時也不存在線程安全不安全的問題了。
2、當用在單例對象成員變量的時候呢?這時候多個線程過來訪問的就是同一個 HashMap 了,對同個 HashMap 操作這時候就存在線程安全的問題了。
29、什么是 CAS 算法?在多線程中有哪些應用。
CAS,全稱為 Compare and Swap,即比較-替換。假設有三個操作數:內存值 V、 舊的預期值 A、要修改的值 B,當且僅當預期值 A 和內存值 V 相同時,才會將內 存值修改為 B 并返回 true,否則什么都不做并返回 false。當然 CAS 一定要 volatile 變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,否則舊的預期 值 A 對某條線程來說,永遠是一個不會變的值 A,只要某次 CAS 操作失敗,永遠 都不可能成功。
java.util.concurrent.atomic 包下面的 Atom****類都有 CAS 算法的應用。
30、怎么檢測一個線程是否擁有鎖?
java.lang.Thread#holdsLock 方法
31、Jdk 中排查多線程問題用什么命令?
jstack
32、線程同步需要注意什么?
1、盡量縮小同步的范圍,增加系統吞吐量。
2、分布式同步鎖無意義,要使用分布式鎖。
3、防止死鎖,注意加鎖順序。
33、線程 wait()方法使用有什么前提?
要在同步塊中使用。
34、Fork/Join 框架使用有哪些要注意的地方?
如果任務拆解的很深,系統內的線程數量堆積,導致系統性能性能嚴重下降; 如果函數的調用棧很深,會導致棧內存溢出。
35、線程之間如何傳遞數據?
通過在線程之間共享對象就可以了 , 然后通過 wait/notify/notifyAll 、 await/signal/signalAll 進行喚起和等待,比方說阻塞隊列 BlockingQueue 就是為線程 之間共享數據而設計的。
36、保證"可見性"有哪幾種方式?
synchronized 和 viotatile
37、說幾個常用的 Lock 接口實現鎖。
ReentrantLock、ReadWriteLock
38、ThreadLocal 是什么?有什么應用場景?
ThreadLocal 的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作 用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。用來 解決數據庫連接、Session 管理等。
39、ReadWriteLock 有什么用?
ReadWriteLock 是一個讀寫鎖接口,ReentrantReadWriteLock 是 ReadWriteLock 接 口的一個具體實現,實現了讀寫的分離,讀鎖是共享的,寫鎖是獨占的,讀和讀之 間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能。
40、FutureTask 是什么?
FutureTask 表示一個異步運算的任務,FutureTask 里面可以傳入一個 Callable 的具體實現類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、 取消任務等操作。
41、怎么喚醒一個阻塞的線程?
如果線程是因為調用了 wait()、sleep()或者 join()方法而導致的阻塞,可以中斷線程, 并且通過拋出 InterruptedException 來喚醒它;如果線程遇到了 IO 阻塞,無能為力, 因為 IO是操作系統實現的,Java 代碼并沒有辦法直接接觸到操作系統。
42、不可變對象對多線程有什么幫助?
不可變對象保證了對象的內存可見性,對不可變對象的讀取不需要進行額外的同步 手段,提升了代碼執行效率。
43、多線程上下文切換是什么意思?
多線程的上下文切換是指 CPU 控制權由一個已經正在運行的線程切換到另外一個 就緒并等待獲取 CPU 執行權的線程的過程。
44、Java 中用到了什么線程調度算法?
搶占式。一個線程用完 CPU 之后,操作系統會根據線程優先級、線程饑餓情況等 數據算出一個總的優先級并分配下一個時間片給某個線程執行。
45、Thread.sleep(0)的作用是什么?
由于 Java 采用搶占式的線程調度算法,因此可能會出現某條線程常常獲取到 CPU 控制權的情況,為了讓某些優先級比較低的線程也能獲取到 CPU 控制權,可以使用Thread.sleep(0)手動觸發一次操作系統分配時間片的操作,這也是平衡 CPU 控制 權的一種操作。
46、Java 內存模型是什么,哪些區域是線程共享的,哪些是不共享的?
我們知道的 JVM 內存區域有:堆和棧,這是一種泛的分法,也是按運行時區域的 一種分法,堆是所有線程共享的一塊區域,而棧是線程隔離的,每個線程互不共享。 線程不共享區域每個線程的數據區域包括程序計數器、虛擬機棧和本地方法棧,它 們都是在新線程創建時才創建的。
程序計數器(ProgramCounterRerister)
程序計數器區域一塊內存較小的區域,它用于存儲線程的每個執行指令,每個線程 都有自己的程序計數器,此區域不會有內存溢出的情況。
虛擬機棧(VMStack)
虛擬機棧描述的是 Java 方法執行的內存模型,每個方法被執行的時候都會同時創 建一個棧幀(StackFrame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口 等信息。每一個方法被調用直至執行完成的過程就對應著一個棧幀在虛擬機棧中從 入棧到出棧的過程。
本地方法棧(NativeMethodStack)
本地方法棧用于支持本地方法(native 標識的方法,即非 Java 語言實現的方法)。 虛擬機棧和本地方法棧,當線程請求分配的棧容量超過 JVM 允許的最大容量時拋出tackOverflowError 異常。
線程共享區域
線程共享區域包含:堆和方法區。
堆(Heap)
堆是最常處理的區域,它存儲在 JVM 啟動時創建的數組和對象,JVM 垃圾收集也 主要是在堆上面工作。
如果實際所需的堆超過了自動內存管理系統能提供的最大容量時拋出OutOfMemoryError 異常。
方法區(MethodArea)
方法區是可供各條線程共享的運行時內存區域。存儲了每一個類的結構信息,例如運行時常量池(Runtime Constant Pool)、字段和方法數據、構造函數和普通方法的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法。當創建類 和接口時,如果構造運行時常量池所需的內存空間超過了方法區所能提供的最大內 存空間后就會拋出OutOfMemoryError
運行時常量池(RuntimeConstantPool)
運行時常量池是方法區的一部分,每一個運行時常量池都分配在 JVM 的方法區中, 在類和接口被加載到 JVM 后,對應的運行時常量池就被創建。運行時常量池是每 一個類或接口的常量池(Constant_Pool)的運行時表現形式,它包括了若干種常量: 編譯器可知的數值字面量到必須運行期解析后才能獲得的方法或字段的引用。如果 方法區的內存空間不能滿足內存分配請求,那 Java 虛 擬 機 將 拋 出 一 個 OutOfMemoryError 異常。棧包含 Frames,當調用方法時,Frame 被推送到堆棧。 一個 Frame 包含局部變量數組、操作數棧、常量池引用。
47、什么是樂觀鎖和悲觀鎖?
樂觀鎖: 就像它的名字一樣,對于并發間操作產生的線程安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作為 一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生沖突,那么就應該有 相應的重試邏輯。
悲觀鎖: 還是像它的名字一樣,對于并發間操作產生的線程安全問題持悲觀狀態, 悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨占的 鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。
48、Hashtable 的 size()方法為什么要做同步?
同一時間只能有一條線程執行固定類的同步方法,但是對于類的非同步方法,可以多條線程同時訪問。所以,這樣就有問題了,可能線程 A 在執行 Hashtable 的 put 方法添加數據,線程 B 則可以正常調用 size()方法讀取 Hashtable 中當前元素的個 數,那讀取到的值可能不是最新的,可能線程 A 添加了完了數據,但是沒有對 size++,線程 B 就已經讀取 size了,那么對于線程 B 來說讀取到的 size 一定是不準 確的。而給 size()方法加了同步之后,意味著線程 B 調用 size()方法只有在線程 A 調用 put 方法完畢之后才可以調用,這樣就保證了線程安全性CPU 執行代碼,執行的不是 Java 代碼,這點很關鍵,一定得記住。Java 代碼最終是被翻譯成機器碼執 行的,機器碼才是真正可以和硬件電路交互的代碼。即使你看到 Java 代碼只有一 行,甚至你看到 Java 代碼編譯之后生成的字節碼也只有一行,也不意味著對于底 層來說這句語句的操作只有一個。一句"return count"假設被翻譯成了三句匯編語句 執行,一句匯編語句和其機器碼做對應,完全可能執行完第一句,線程就切換了。
49、同步方法和同步塊,哪種更好?
同步塊,這意味著同步塊之外的代碼是異步執行的,這比同步整個方法更提升代碼 的效率。 請知道一條原則:同步的范圍越小越好。
50、什么是自旋鎖?
自旋鎖是采用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線 程改變時才能進入臨界區。
51、Runnable 和 Thread 用哪個好?
Java 不支持類的多重繼承,但允許你實現多個接口。所以如果你要繼承其他類,也為了減少類之間的耦合性,Runnable 會更好。
52、Java 中 notify 和 notifyAll 有什么區別?
notify()方法不能喚醒某個具體的線程,所以只有一個線程在等待的時候它才有用武之地。
而 notifyAll()喚醒所有線程并允許他們爭奪鎖確保了至少有一個線程能繼續運行。
53、為什么 wait/notify/notifyAll 這些方法不在 thread 類里面?
這是個設計相關的問題,它考察的是面試者對現有系統和一些普遍存在但看起來不 合理的事物的看法。回答這些問題的時候,你要說明為什么把這些方法放在 Object 類里是有意義的,還有不把它放在 Thread 類里的原因。一個很明顯的原因是 JAVA 提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那么調用對象中的wait()方法就有意義了。如果 wait()方法定 義在 Thread 類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由于 wait, notify 和 notifyAll 都是鎖級別的操作,所以把他們定義Object 類中因為鎖屬于對象。
54、為什么 wait 和 notify 方法要在同步塊中調用?
主要是因為Java API 強 制 要 求 這 樣 做 , 如果你不這么做 , 代碼會拋出IllegalMonitorStateException 異常。還有一個原因是為了避免 wait 和 notify 之間產生競態條件。
55、為什么你應該在循環中檢查等待條件?
處于等待狀態的線程可能會收到錯誤警報和偽喚醒,如果不在循環中檢查等待條件, 程序就會在沒有滿足結束條件的情況下退出。因此,當一個等待線程醒來時,不能 認為它原來的等待狀態仍然是有效的,在 notify()方法調用之后和等待線程醒來之 前這段時間它可能會改變。這就是在循環中使用 wait()方法效果更好的原因,你可 以在 Eclipse 中創建模板調用 wait和 notify 試一試。
56、Java 中堆和棧有什么不同?
每個線程都有自己的棧內存,用于存儲本地變量,方法參數和棧調用,一個線程中 存儲的變量對其它線程是不可見的。而堆是所有線程共享的一片公用內存區域。對象都在堆里創建,為了提升效率線程會從堆中弄一個緩存到自己的棧,如果多個線 程使用該變量就可能引發問題,這時 volatile 變量就可以發揮作用了,它要求線程 從主存中讀取變量的值。
57、你如何在 Java 中獲取線程堆棧?
對于不同的操作系統,有多種方法來獲得 Java 進程的線程堆棧。當你獲取線程堆 棧時,JVM會把所有線程的狀態存到日志文件或者輸出到控制臺。在 Windows 你 可以使用 Ctrl + Break 組合鍵來獲取線程堆棧,Linux 下用 kill -3 命令。你也可以 用 jstack 這個工具來獲取,它對線程 id 進行操作,你可以用 jps 這個工具找到 id。
58、如何創建線程安全的單例模式?
單例模式即一個 JVM 內存中只存在一個類的對象實例分類
1、懶漢式
使用的時候才創建實例
2、餓漢式
類加載的時候就創建實例
59、什么是阻塞式方法?
阻塞式方法是指程序會一直等待該方法完成期間不做其他事情,ServerSocket 的accept()方法就是一直等待客戶端連接。這里的阻塞是指調用結果返回之前,當前 線程會被掛起,直到得到結果之后才會返回。此外,還有異步和非阻塞式方法在任 務完成前就返回
60、提交任務時線程池隊列已滿會時發會生什么?
當線程數小于最大線程池數 maximumPoolSize 時就會創建新線程來處理,而線程 數大于等于最大線程池數 maximumPoolSize 時就會執行拒絕策略。
————————————————
版權聲明:本文為CSDN博主「amelia_」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/xxyybs/article/details/102793030
總結
以上是生活随笔為你收集整理的面试:史上最全多线程面试题 - (锁内存模型线程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 个人房贷申请条件
- 下一篇: git 删除本地分支和远程分支、本地代码