1. 程式人生 > >Java多執行緒精選面試題

Java多執行緒精選面試題

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終止(不推薦)

使用volatile標誌位 , public volatile boolean flag = true;

使用interrupt()中斷的方式

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鎖實現,

分散式鎖:

基於資料庫實現分散式鎖; 
基於快取(Redis等)實現分散式鎖; 
基於Zookeeper實現分散式鎖;

9、什麼是死鎖?如何避免死鎖?

死鎖就是兩個執行緒相互等待對方釋放物件鎖。

10、多執行緒之間如何進行通訊?

wait/notify

11、執行緒怎樣拿到返回結果?

實現Callable介面。

12、violatile關鍵字的作用?

一個非常重要的問題,是每個學習、應用多執行緒的Java程式設計師都必須掌握的。

關鍵字的作用的前提是要理解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和作業系統都會對指令進行重排,也就說前面的程式碼並不一定都會在後面的程式碼前面執行,即後面的程式碼可能會插到前面的程式碼之前執行,只要不影響當前執行緒的執行結果。所以,指令重排只會保證當前執行緒執行結果一致,但勢必會影響多執行緒的執行結果。

雖然重排序優化了效能,但也是會遵守一些規則的,並不能隨便亂排序,只是重排序會影響多執行緒執行的結果。

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記憶體區域有:堆和棧,這是一種泛的分法,也是按執行時區域的一種分法,堆是所有執行緒共享的一塊區域,而棧是執行緒隔離的,每個執行緒互不共享。

1. 執行緒不共享區域

每個執行緒的資料區域包括程式計數器、虛擬機器棧和本地方法棧,它們都是在新執行緒建立時才建立的。

程式計數器(Program Counter Rerister) 

程式計數器區域一塊記憶體較小的區域,它用於儲存執行緒的每個執行指令,每個執行緒都有自己的程式計數器,此區域不會有記憶體溢位的情況。

虛擬機器棧(VM Stack)

虛擬機器棧描述的是Java方法執行的記憶體模型,每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。

本地方法棧(Native Method Stack)

本地方法棧用於支援本地方法(native標識的方法,即非Java語言實現的方法)。

虛擬機器棧和本地方法棧,當執行緒請求分配的棧容量超過JVM允許的最大容量時丟擲StackOverflowError異常。

執行緒不共享區域如下圖綠色背景所示。

2. 執行緒共享區域

執行緒共享區域包含:堆和方法區。

堆(Heap)

堆是最常處理的區域,它儲存在JVM啟動時建立的陣列和物件,JVM垃圾收集也主要是在堆上面工作。

如果實際所需的堆超過了自動記憶體管理系統能提供的最大容量時丟擲OutOfMemoryError異常。

方法區(Method Area)

方法區是可供各條執行緒共享的執行時記憶體區域。儲存了每一個類的結構資訊,例如執行時常量池(Runtime Constant Pool)、欄位和方法資料、建構函式和普通方法的位元組碼內容、還包括一些在類、例項、介面初始化時用到的特殊方法。

當建立類和介面時,如果構造執行時常量池所需的記憶體空間超過了方法區所能提供的最大記憶體空間後就會丟擲OutOfMemoryError

3. 執行時常量池(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不支援類的多重繼承,但允許你實現多個介面。所以如果你要繼承其他類,也為了減少類之間的耦合性,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時就會執行拒絕策略。