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 等關鍵字等佔用臨界區的情況,一旦獲取到鎖就進行 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()的方式來建立並執行一個執行緒,執行緒少的話這不會是問題,而真實環境可能會開啟多個執行緒讓系統和程式達到最佳效率,當執行緒數達到一定數量就會耗盡系統的 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 保證原子性。
-
可見性 可見性是指某個執行緒修改了某一個共享變數的值,而其他執行緒是否可以看見該共享變數修改後的值。在單執行緒中肯定不會有這種問題,單執行緒讀到的肯定都是最新的值,而在多執行緒程式設計中就不一定了。 每個執行緒都有自己的工作記憶體,執行緒先把共享變數的值從主記憶體讀到工作記憶體,形成一個副本,當計算完後再把副本的值刷回主記憶體,從讀取到最後刷回主記憶體這是一個過程,當還沒刷回主記憶體的時候這時候對其他執行緒是不可見的,所以其他執行緒從主記憶體讀到的值是修改之前的舊值。像 CPU 的快取優化、硬體優化、指令重排及對 JVM 編譯器的優化,都會出現可見性的問題。
-
有序性 我們都知道程式是按程式碼順序執行的,對於單執行緒來說確實是如此,但在多執行緒情況下就不是如此了。為了優化程式執行和提高 CPU 的處理效能,JVM 和作業系統都會對指令進行重排,也就說前面的程式碼並不一定都會在後面的程式碼前面執行,即後面的程式碼可能會插到前面的程式碼之前執行,只要不影響當前執行緒的執行結果。所以,指令重排只會保證當前執行緒執行結果一致,但指令重排後勢必會影響多執行緒的執行結果。雖然重排序優化了效能,但也是會遵守一些規則的,並不能隨便亂排序,只是重排序會影響多執行緒執行的結果。
-
什麼是守護執行緒? 與守護執行緒相對應的就是使用者執行緒,守護執行緒就是守護使用者執行緒,當使用者執行緒全部執行完結束之後,守護執行緒才會跟著結束。也就是守護執行緒必須伴隨著使用者執行緒,如果一個應用內只存在一個守護執行緒,沒有使用者執行緒,守護執行緒自然會退出。
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、什麼是樂觀鎖和悲觀鎖?
- 樂觀鎖:就像它的名字一樣,對於併發間操作產生的執行緒安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作為一個原子操作嘗試去修改記憶體中的變數,如果失敗則表示發生衝突,那麼就應該有相應的重試邏輯。
- 悲觀鎖:還是像它的名字一樣,對於併發間操作產生的執行緒安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨佔的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。
47、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"假設被翻譯成了三句彙編語句執行,一句彙編語句和其機器碼做對應,完全可能執行完第一句,執行緒就切換了。
48、同步方法和同步塊,哪種更好?
- 同步塊,這意味著同步塊之外的程式碼是非同步執行的,這比同步整個方法更提升程式碼的效率。請知道一條原則:同步的範圍越小越好。
49、什麼是自旋鎖?
- 自旋鎖是採用讓當前執行緒不停地的在迴圈體內執行實現的,當迴圈的條件被其他執行緒改變時 才能進入臨界區。
50、Runnable 和 Thread 用哪個好?
- Java 不支援類的多重繼承,但允許你實現多個介面。所以如果你要繼承其他類,也為了減 少類之間的耦合性,Runnable 會更好。
51、Java 中 notify 和 notifyAll 有什麼區別?
- notify()方法不能喚醒某個具體的執行緒,所以只有一個執行緒在等待的時候它才有用武之地。而 notifyAll()喚醒所有執行緒並允許他們爭奪鎖確保了至少有一個執行緒能繼續執行。
52、為什麼 wait/notify/notifyAll 這些方法不在 thread 類裡面?
- 這是個設計相關的問題,它考察的是面試者對現有系統和一些普遍存在但看起來不合理的事物的看法。回答這些問題的時候,你要說明為什麼把這些方法放在 Object 類裡是有意義的,還有不把它放在 Thread 類裡的原因。一個很明顯的原因是 JAVA 提供的鎖是物件級的而不是執行緒級的,每個物件都有鎖,通過執行緒獲得。如果執行緒需要等待某些鎖那麼呼叫物件中的wait()方法就有意義了。如果 wait()方法定義在 Thread 類中,執行緒正在等待的是哪個鎖就不明顯了。簡單的說,由於 wait,notify 和 notifyAll 都是鎖級別的操作,所以把他們定義在 Object 類中因為鎖屬於物件。
53、為什麼 wait 和 notify 方法要在同步塊中呼叫?
- 主 要 是 因 為 Java API 強 制 要 求 這 樣 做 , 如 果 你 不 這 麼 做 , 你 的 代 碼 會 拋 出IllegalMonitorStateException 異常。還有一個原因是為了避免 wait 和 notify 之間產生競態條件。
54、為什麼你應該在迴圈中檢查等待條件?
- 處於等待狀態的執行緒可能會收到錯誤警報和偽喚醒,如果不在迴圈中檢查等待條件,程式就會在沒有滿足結束條件的情況下退出。因此,當一個等待執行緒醒來時,不能認為它原來的等待狀態仍然是有效的,在 notify()方法呼叫之後和等待執行緒醒來之前這段時間它可能會改變。這就是在迴圈中使用 wait()方法效果更好的原因,你可以在 Eclipse 中建立模板呼叫 wait和 notify 試一試。
55、Java 中堆和棧有什麼不同?
- 每個執行緒都有自己的棧記憶體,用於儲存本地變數,方法引數和棧呼叫,一個執行緒中儲存的變數對其它執行緒是不可見的。而堆是所有執行緒共享的一片公用記憶體區域。物件都在堆裡建立,為了提升效率執行緒會從堆中弄一個快取到自己的棧,如果多個執行緒使用該變數就可能引發問題,這時 volatile 變數就可以發揮作用了,它要求執行緒從主存中讀取變數的值。
56、你如何在 Java 中獲取執行緒堆疊?
- 對於不同的作業系統,有多種方法來獲得 Java 程式的執行緒堆疊。當你獲取執行緒堆疊時,JVM會把所有執行緒的狀態存到日誌檔案或者輸出到控制檯。在 Windows 你可以使用 Ctrl +Break 組合鍵來獲取執行緒堆疊,Linux 下用 kill -3 命令。你也可以用 jstack 這個工具來獲取,它對執行緒 id 進行操作,你可以用 jps 這個工具找到 id。
57、如何建立執行緒安全的單例模式?
- 單例模式即一個 JVM 記憶體中只存在一個類的物件例項分類
- 1、懶漢式 類載入的時候就建立例項
- 2、餓漢式 使用的時候才建立例項
58、什麼是阻塞式方法?
- 阻塞式方法是指程式會一直等待該方法完成期間不做其他事情,ServerSocket 的 accept()方法就是一直等待客戶端連線。這裡的阻塞是指呼叫結果返回之前,當前執行緒會被掛起,直到得到結果之後才會返回。此外,還有非同步和非阻塞式方法在任務完成前就返回。
59、提交任務時執行緒池佇列已滿會時發會生什麼?
- 當執行緒數小於最大執行緒池數 maximumPoolSize 時就會建立新執行緒來處理,而執行緒數大於等於最大執行緒池數 maximumPoolSize 時就會執行拒絕策略。