最通俗易懂的多線程面試60題

多線程面試60題

僅供學習和參考

1.多線程有什麼用?

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,分別建立程序模型,並通過多線程分別運行這幾個任務,那就簡單很多了。

2.線程和進程的區別是什麼?

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地 址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的 地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進 行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

3.Java 實現線程有哪幾種方式?

1)繼承 Thread 類實現多線程
2)實現 Runnable 接口方式實現多線程
3)使用 ExecutorService、Callable、Future 實現有返回結果的多線程

4.啓動線程方法 start()和 run()有什麼區別?

只有調用了 start()方法,纔會表現出多線程的特性,不同線程的 run()方法裏面的代 碼交替執行。如果只是調用 run()方法,那麼代碼還是同步執行的,必須等待一個 線程的 run()方法裏面的代碼全部執行完畢之後,另外一個線程纔可以執行其 run() 方法裏面的代碼。

5.怎麼終止一個線程?

如何優雅地終止線程? stop 終止 ,不推薦。

6.一個線程的生命週期有哪幾種狀態?它們之間如何流轉的?

NEW:毫無疑問表示的是剛創建的線程,還沒有開始啓動。
RUNNABLE: 表示線程已經觸發 start()方式調用,線程正式啓動,線程處於運行中 狀態。 BLOCKED:表示線程阻塞,等待獲取鎖,如碰到 synchronized、lock 等關鍵字等12佔用臨界區的情況,一旦獲取到鎖就進行 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.什麼是死鎖?如何避免死鎖?

死鎖就是兩個線程相互等待對方釋放對象鎖。

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()的方 式來創建並運行一個線程,線程少的話這不會是問題,而真實環境可能會開啓多個13線程讓系統和程序達到最佳效率,當線程數達到一定數量就會耗盡系統的 CPU 和 內存資源,也會造成 GC頻繁收集和停頓,因爲每次創建和銷燬一個線程都是要消 耗系統資源的,如果爲每個任務都創建線程這無疑是一個很大的性能瓶頸。所以, 線程池中的線程複用極大節省了系統資源,當線程一段時間不再有任務處理時它也 會自動銷燬,而不會長駐內存。

16、常用的幾種線程池並講講其中的工作原理。

什麼是線程池?

很簡單,簡單看名字就知道是裝有線程的池子,我們可以把要執行的多線程交給線 程池來處理,和連接池的概念一樣,通過維護一定數量的線程池來達到多個線程的複用。

線程池的好處

我們知道不用線程池的話,每個線程都要通過 new Thread(xxRunnable).start()的方 式來創建並運行一個線程,線程少的話這不會是問題,而真實環境可能會開啓多個 線程讓系統和程序達到最佳效率,當線程數達到一定數量就會耗盡系統的 CPU 和 內存資源,也會造成 GC頻繁收集和停頓,因爲每次創建和銷燬一個線程都是要消 耗系統資源的,如果爲每個任務都創建線程這無疑是一個很大的性能瓶頸。所以, 線程池中的線程複用極大節省了系統資源,當線程一段時間不再有任務處理時它也 會自動銷燬,而不會長駐內存。 線程池核心類 在 java.util.concurrent 包中我們能找到線程池的定義,其中 ThreadPoolExecutor 是 我們線程池核心類,首先看看線程池類的主要參數有哪些。

如何提交線程

如 可 以 先 隨 便 定 義 一 個 固 定 大 小 的 線 程 池 ExecutorService es = Executors.newFixedThreadPool(3);
提交一個線程es.submit(xxRunnble); es.execute(xxRunnble);

submit 和 execute 分別有什麼區別呢?

execute 沒有返回值,如果不需要知道線程的結果就使用 execute 方法,性能會好很 多。submit 返回一個 Future 對象,如果想知道線程結果就使用 submit 提交,而且它能 在主線程中通過 Future 的 get 方法捕獲線程中的異常。

如何關閉線程池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、什麼是活鎖、飢餓、無鎖、死鎖?

死鎖、活鎖、飢餓是關於多線程是否活躍出現的運行阻塞障礙問題,如果線程出現 了這三種情況,即線程不再活躍,不能再正常地執行下去了。
死鎖是多線程中最差的一種情況,多個線程相互佔用對方的資源的鎖,而又相互等 對方釋放鎖,此時若無外力干預,這些線程則一直處理阻塞的假死狀態,形成死鎖。 舉個例子,A 同學搶了 B 同學的鋼筆,B 同學搶了 A 同學的書,兩個人都相互佔 用對方的東西,都在讓對方先還給自己自己再還,這樣一直爭執下去等待對方還而 又得不到解決,老師知道此事後就讓他們相互還給對方,這樣在外力的干預下他們 才解決,當然這只是個例子沒有老師他們也能很好解決,計算機不像人如果發現這 種情況沒有外力干預還是會一直阻塞下去的。
活鎖這個概念大家應該很少有人聽說或理解它的概念,而在多線程中這確實存在。 活鎖恰恰與死鎖相反,死鎖是大家都拿不到資源都佔用着對方的資源,而活鎖是拿 到資源卻又相互釋放不執行。當多線程中出現了相互謙讓,都主動將資源釋放給別 的線程使用,這樣這個資源在多個線程之間跳動而又得不到執行,這就是活鎖。
飢餓我們知道多線程執行中有線程優先級這個東西,優先級高的線程能夠插隊並優先執 行,這樣如果優先級高的線程一直搶佔優先級低線程的資源,導致低優先級線程無 法得到執行,這就是飢餓。當然還有一種飢餓的情況,一個線程一直佔着一個資源 不放而導致其他線程得不到執行,與死鎖不同的是飢餓在以後一段時間內還是能夠 得到執行的,如那個佔用資源的線程結束了並釋放了資源。
無鎖,即沒有對資源進行鎖定,即所有的線程都能訪問並修改同一個資源,但同時 只有一個線程能修改成功。無鎖典型的特點就是一個修改操作在一個循環內進行, 線程會不斷的嘗試修改共享資源,如果沒有衝突就修改成功並退出否則就會繼續下 一次循環嘗試。所以,如果有多個線程修改同一個值必定會有一個線程能修改成功, 而其他修改失敗的線程會不斷重試直到修改成功。之前的文章我介紹過 JDK 的 CAS 原理及應用即是無鎖的實現。 可以看出,無鎖是一種非常良好的設計,它不會出現線程出現的跳躍性問題,鎖使 用不當肯定會出現系統性能問題,雖然無鎖無法全面代替有鎖,但無鎖在某些場合 下是非常高效的。

20、什麼是原子性、可見性、有序性?

原子性、可見性、有序性是多線程編程中最重要的幾個知識點,由於多線程情況復 雜,如何讓每個線程能看到正確的結果,這是非常重要的。
原子性是指一個線程的操作是不能被其他線程打斷,同一時間只有一個線程對一個 變量進行操作。在多線程情況下,每個線程的執行結果不受其他線程的干擾,比如 說多個線程同時對同一個共享成員變量 n++100 次,如果 n 初始值爲 0,n 最後的 值應該是 100,所以說它們是互不干擾的,這就是傳說的中的原子性。但 n++並不 是原子性的操作,要使用 AtomicInteger 保證原子性。
可見性是指某個線程修改了某一個共享變量的值,而其他線程是否可以看見該共享 變量修改後的值。在單線程中肯定不會有這種問題,單線程讀到的肯定都是最新的15值,而在多線程編程中就不一定了。每個線程都有自己的工作內存,線程先把共享 變量的值從主內存讀到工作內存,形成一個副本,當計算完後再把副本的值刷回主 內存,從讀取到最後刷回主內存這是一個過程,當還沒刷回主內存的時候這時候對 其他線程是不可見的,所以其他線程從主內存讀到的值是修改之前的舊值。像 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 內存區域有:堆和棧,這是一種泛的分法,也是按運行時區域的一種分法,堆是所有線程共享的一塊區域,而棧是線程隔離的,每個線程互不共享。線程不共享區域每個線程的數據區域包括程序計數器、虛擬機棧和本地方法棧,它們都是在新線程創建時才創建的。程序計數器(Program Counter Rerister)程序計數器區域一塊內存較小的區域,它用於存儲線程的每個執行指令,每個線程都有自己的程序計數器,此區域不會有內存溢出的情況。
虛擬機棧(VM Stack
虛擬機棧描述的是 Java 方法執行的內存模型,每個方法被執行的時候都會同時創建一個棧幀(StackFrame)用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
本地方法棧(Native Method Stack)
本地方法棧用於支持本地方法(native 標識的方法,即非 Java 語言實現的方法)。
虛擬機棧和本地方法棧,當線程請求分配的棧容量超過 JVM 允許的最大容量時拋出StackOverflowError 異常。
線程共享區域
線程共享區域包含:堆和方法區。
堆(Heap)
堆是最常處理的區域,它存儲在 JVM 啓動時創建的數組和對象,JVM 垃圾收集也主要是在堆上面工作。
如 果 實 際 所 需 的 堆 超 過 了 自 動 內 存 管 理 系 統 能 提 供 的 最 大 容 量 時 拋 出18OutOfMemoryError 異常。
方法區(Method Area)
方法區是可供各條線程共享的運行時內存區域。存儲了每一個類的結構信息,例如 運行時常量池(Runtime Constant Pool)、字段和方法數據、構造函數和普通方法 的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法。當創建類 和接口時,如果構造運行時常量池所需的內存空間超過了方法區所能提供的最大內 存空間後就會拋出 OutOfMemoryError 運行時常量池(Runtime Constant Pool) 運行時常量池是方法區的一部分,每一個運行時常量池都分配在 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 不支持類的多重繼承,但允許你實現多個接口。所以如果你要繼承其他類,也19爲了減少類之間的耦合性,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 的110accept()方法就是一直等待客戶端連接。這裏的阻塞是指調用結果返回之前,當前 線程會被掛起,直到得到結果之後纔會返回。此外,還有異步和非阻塞式方法在任 務完成前就返回。

60、提交任務時線程池隊列已滿會時發會生什麼?

當線程數小於最大線程池數 maximumPoolSize 時就會創建新線程來處理,而線程 數大於等於最大線程池數 maximumPoolSize 時就會執行拒絕策略。

如果有什麼問題可聯繫[email protected]!!!

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