http://wenku.baidu.com/link?url=-7RVShQCzkHaRbyPdGP-oeVXgZ7t4mS2oqXevpzweesI_Heof5zqc_Wsvhhi8pZSU7xQWEp6e_XO7ch5nWaU_-x9Ja0CN_5V3K6QNlnBJKe
/
http://blog.jobbole.com/18571/
Java 線程面試問題
在任何Java面試當中多線程和併發方面的問題都是必不可少的一部分。如果你想獲得任何股票投資銀行的前臺資訊職位,那麼你應該準備很多關於多線程的問題。在投資銀行業務中多線程和併發是一個非常受歡迎的話題,特別是電子交易發展方面相關的。他們會問面試者很多令人混淆的Java線程問題。面試官只是想確信面試者有足夠的Java線程與併發方面的知識,因爲候選人中有很多隻浮於表面。用於直接面向市場交易的高容量和低延時的電子交易系統在本質上是併發的。下面這些是我在不同時間不同地點喜歡問的Java線程問題。我沒有提供答案,但只要可能我會給你線索,有些時候這些線索足夠回答問題。現在引用Java5併發包關於併發工具和併發集合的問題正在增多。那些問題中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比較流行。
15個Java多線程面試題及回答
1)現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?
這個線程問題通常會在第一輪或電話面試階段被問到,目的是檢測你對”join”方法是否熟悉。這個多線程問題比較簡單,可以用join方法實現。
2)在Java中Lock接口比synchronized塊的優勢是什麼?你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?
lock接口在多線程和併發編程中最大的優勢是它們爲讀和寫分別提供了鎖,它能滿足你寫像ConcurrentHashMap這樣的高性能數據結構和有條件的阻塞。Java線程面試的問題越來越會根據面試者的回答來提問。我強烈建議在你去參加多線程的面試之前認真讀一下Locks,因爲當前其大量用於構建電子交易終統的客戶端緩存和交易連接空間。
3)在java中wait和sleep方法的不同?
通常會在電話面試中經常被問到的Java線程面試問題。最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於線程間交互,sleep通常被用於暫停執行。
4)用Java實現阻塞隊列。
這是一個相對艱難的多線程面試問題,它能達到很多的目的。第一,它可以檢測侯選者是否能實際的用Java線程寫程序;第二,可以檢測侯選者對併發場景的理解,並且你可以根據這個問很多問題。如果他用wait()和notify()方法來實現阻塞隊列,你可以要求他用最新的Java 5中的併發類來再寫一次。
5)用Java寫代碼來解決生產者——消費者問題。
與上面的問題很類似,但這個問題更經典,有些時候面試都會問下面的問題。在Java中怎麼解決生產者——消費者問題,當然有很多解決方法,我已經分享了一種用阻塞隊列實現的方法。有些時候他們甚至會問怎麼實現哲學家進餐問題。
6)用Java編程一個會導致死鎖的程序,你將怎麼解決?
這是我最喜歡的Java線程面試問題,因爲即使死鎖問題在寫多線程併發程序時非常普遍,但是很多侯選者並不能寫deadlock free code(無死鎖代碼?),他們很掙扎。只要告訴他們,你有N個資源和N個線程,並且你需要所有的資源來完成一個操作。爲了簡單這裏的n可以替換爲2,越大的數據會使問題看起來更復雜。通過避免Java中的死鎖來得到關於死鎖的更多信息。
7) 什麼是原子操作,Java中的原子操作是什麼?
非常簡單的java線程面試問題,接下來的問題是你需要同步一個原子操作。
8) Java中的volatile關鍵是什麼作用?怎樣使用它?在Java中它跟synchronized方法有什麼不同?
自從Java 5和Java內存模型改變以後,基於volatile關鍵字的線程問題越來越流行。應該準備好回答關於volatile變量怎樣在併發環境中確保可見性、順序性和一致性。
-----------------------------------
Java
關鍵字volatile 與 synchronized 作用與區別
1,volatile
它所修飾的變量不保留拷貝,直接訪問主內存中的。
在Java內存模型中,有main memory,每個線程也有自己的memory (例如寄存器)。爲了性能,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現同一個變 量在某個瞬間,在一個線程的memory中的值可能與另外一個線程memory中的值,或者main memory中的值不一致的情況。 一個變量聲明爲volatile,就意味着這個變量是隨時會被其他線程修改的,因此不能將它cache在線程memory中。
2,synchronized
當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。
一、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
五、以上規則對其它對象鎖同樣適用.
區別:
一、volatile是變量修飾符,而synchronized則作用於一段代碼或方法。
二、volatile只是在線程內存和“主”內存間同步某個變量的值;而synchronized通過鎖定和解鎖某個監視器同步所有變量的值。顯然synchronized要比volatile消耗更多資源
--------------------------------------------------------------------------------------------------
9) 什麼是競爭條件?你怎樣發現和解決競爭?
這是一道出現在多線程面試的高級階段的問題。大多數的面試官會問最近你遇到的競爭條件,以及你是怎麼解決的。有些時間他們會寫簡單的代碼,然後讓你檢測出代碼的競爭條件。可以參考我之前發佈的關於Java競爭條件的文章。在我看來這是最好的java線程面試問題之一,它可以確切的檢測候選者解決競爭條件的經驗,or writing code which is free of data race or any other race condition。關於這方面最好的書是《Concurrency practices in Java》。
10) 你將如何使用thread dump?你將如何分析Thread dump?
在UNIX中你可以使用kill -3,然後thread dump將會打印日誌,在windows中你可以使用”CTRL+Break”。非常簡單和專業的線程面試問題,但是如果他問你怎樣分析它,就會很棘手。
11) 爲什麼我們調用start()方法時會執行run()方法,爲什麼我們不能直接調用run()方法?
這是另一個非常經典的java多線程面試問題。這也是我剛開始寫線程程序時候的困惑。現在這個問題通常在電話面試或者是在初中級Java面試的第一輪被問到。這個問題的回答應該是這樣的,當你調用start()方法時你將創建新的線程,並且執行在run()方法裏的代碼。但是如果你直接調用run()方法,它不會創建新的線程也不會執行調用線程的代碼。閱讀我之前寫的《start與run方法的區別》這篇文章來獲得更多信息。
12) Java中你怎樣喚醒一個阻塞的線程?
這是個關於線程和阻塞的棘手的問題,它有很多解決方法。如果線程遇到了IO阻塞,我並且不認爲有一種方法可以中止線程。如果線程因爲調用wait()、sleep()、或者join()方法而導致的阻塞,你可以中斷線程,並且通過拋出InterruptedException來喚醒它。我之前寫的《How to deal with blocking methods in java》有很多關於處理線程阻塞的信息。
13)在Java中CycliBarriar和CountdownLatch有什麼區別?
這個線程問題主要用來檢測你是否熟悉JDK5中的併發包。這兩個的區別是CyclicBarrier可以重複使用已經通過的障礙,而CountdownLatch不能重複使用。
14) 什麼是不可變對象,它對寫併發應用有什麼幫助?
另一個多線程經典面試問題,並不直接跟線程有關,但間接幫助很多。這個java面試問題可以變的非常棘手,如果他要求你寫一個不可變對象,
或者問你爲什麼String是不可變的
-------------------------------------
原文鏈接: Why string is immutable in Java ?
這是一個老生常談的話題(This is an old yet still popular question). 在Java中將String設計成不可變的是綜合考慮到各種因素的結果,想要理解這個問題,需要綜合內存,同步,數據結構以及安全等方面的考慮. 在下文中,我將爲各種原因做一個小結。
1. 字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java堆內存中一個特殊的存儲區域, 當創建一個String對象時,假如此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的對象。
如下面的代碼所示,將會在堆內存中只創建一個實際String對象.
示意圖如下所示:
假若字符串對象允許改變,那麼將會導致各種邏輯錯誤,比如改變一個對象會影響到另一個獨立對象. 嚴格來說,這種常量池的思想,是一種優化手段.
請思考: 假若代碼如下所示,s1和s2還會指向同一個實際的String對象嗎?
也許這個問題違反新手的直覺, 但是考慮到現代編譯器會進行常規的優化, 所以他們都會指向常量池中的同一個對象. 或者,你可以用 jd-gui 之類的工具查看一下編譯後的class文件.
2. 允許String對象緩存HashCode
Java中String對象的哈希碼被頻繁地使用, 比如在hashMap 等容器中。
字符串不變性保證了hash碼的唯一性,因此可以放心地進行緩存.這也是一種性能優化手段,意味着不必每次都去計算新的哈希碼. 在String類的定義中有如下代碼:
3. 安全性
String被許多的Java類(庫)用來當做參數,例如 網絡連接地址URL,文件路徑path,還有反射機制所需要的String參數等, 假若String不是固定不變的,將會引起各種安全隱患。
假如有如下的代碼:
- boolean connect(string s){
- if (!isSecure(s)) {
- throw new SecurityException();
- }
- // 如果在其他地方可以修改String,那麼此處就會引起各種預料不到的問題/錯誤
- causeProblem(s);
- }
相關文章 :
------------------------------------------------------------------------------------------
15) 你在多線程環境中遇到的常見的問題是什麼?你是怎麼解決它的?
多線程和併發程序中常遇到的有Memory-interface、競爭條件、死鎖、活鎖和飢餓。問題是沒有止境的,如果你弄錯了,將很難發現和調試。這是大多數基於面試的,而不是基於實際應用的Java線程問題。
補充的其它幾個問題:
1) 在java中綠色線程和本地線程區別?
2) 線程與進程的區別?
3) 什麼是多線程中的上下文切換?
4)死鎖與活鎖的區別,死鎖與飢餓的區別?
5) Java中用到的線程調度算法是什麼?
6) 在Java中什麼是線程調度?
7) 在線程中你怎麼處理不可捕捉異常?
8) 什麼是線程組,爲什麼在Java中不推薦使用?
9) 爲什麼使用Executor框架比使用應用創建和管理線程好?
10) 在Java中Executor和Executors的區別?
11) 如何在Windows和Linux上查找哪個線程使用的CPU時間最長?
///////////////////////////////
很多核心Java面試題來源於多線程(Multi-Threading)和集合框架(Collections Framework),理解核心線程概念時,嫺熟的實際經驗是必需的。這篇文章收集了Java線程方面一些典型的問題,這些問題經常被高級工程師所問到。
0、Java中多線程同步是什麼?
在多線程程序下,同步能控制對共享資源的訪問。如果沒有同步,當一個Java線程在修改一個共享變量時,另外一個線程正在使用或者更新同一個變量,這樣容易導致程序出現錯誤的結果。
1、解釋實現多線程的幾種方法?
一Java線程可以實現Runnable接口或者繼承Thread類來實現,當你打算多重繼承時,優先選擇實現Runnable。
2、Thread.start()與Thread.run()有什麼區別?
Thread.start()方法(native)啓動線程,使之進入就緒狀態,當cpu分配時間該線程時,由JVM調度執行run()方法。
3、爲什麼需要run()和start()方法,我們可以只用run()方法來完成任務嗎?
我們需要run()&start()這兩個方法是因爲JVM創建一個單獨的線程不同於普通方法的調用,所以這項工作由線程的start方法來完成,start由本地方法實現,需要顯示地被調用,使用這倆個方法的另外一個好處是任何一個對象都可以作爲線程運行,只要實現了Runnable接口,這就避免因繼承了Thread類而造成的Java的多繼承問題。
4、什麼是ThreadLocal類,怎麼使用它?
ThreadLocal是一個線程級別的局部變量,並非“本地線程”。ThreadLocal爲每個使用該變量的線程提供了一個獨立的變量副本,每個線程修改副本時不影響其它線程對象的副本(譯者注)。
下面是線程局部變量(ThreadLocal variables)的關鍵點:
一個線程局部變量(ThreadLocal variables)爲每個線程方便地提供了一個單獨的變量。
ThreadLocal實例通常作爲靜態的私有的(private static)字段出現在一個類中,這個類用來關聯一個線程。
當多個線程訪問ThreadLocal實例時,每個線程維護ThreadLocal提供的獨立的變量副本。
常用的使用可在DAO模式中見到,當DAO類作爲一個單例類時,數據庫鏈接(connection)被每一個線程獨立的維護,互不影響。(基於線程的單例)
ThreadLocal難於理解,下面這些引用連接有助於你更好的理解它。
《Good article on ThreadLocal on IBM DeveloperWorks 》、《理解ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs》
5、什麼時候拋出InvalidMonitorStateException異常,爲什麼?
調用wait()/notify()/notifyAll()中的任何一個方法時,如果當前線程沒有獲得該對象的鎖,那麼就會拋出IllegalMonitorStateException的異常(也就是說程序在沒有執行對象的任何同步塊或者同步方法時,仍然嘗試調用wait()/notify()/notifyAll()時)。由於該異常是RuntimeExcpetion的子類,所以該異常不一定要捕獲(儘管你可以捕獲只要你願意).作爲RuntimeException,此類異常不會在wait(),notify(),notifyAll()的方法簽名提及。
6、Sleep()、suspend()和wait()之間有什麼區別?
Thread.sleep()使當前線程在指定的時間處於“非運行”(Not Runnable)狀態。線程一直持有對象的監視器。比如一個線程當前在一個同步塊或同步方法中,其它線程不能進入該塊或方法中。如果另一線程調用了interrupt()方法,它將喚醒那個“睡眠的”線程。
注意:sleep()是一個靜態方法。這意味着只對當前線程有效,一個常見的錯誤是調用t.sleep(),(這裏的t是一個不同於當前線程的線程)。即便是執行t.sleep(),也是當前線程進入睡眠,而不是t線程。t.suspend()是過時的方法,使用suspend()導致線程進入停滯狀態,該線程會一直持有對象的監視器,suspend()容易引起死鎖問題。
object.wait()使當前線程出於“不可運行”狀態,和sleep()不同的是wait是object的方法而不是thread。調用object.wait()時,線程先要獲取這個對象的對象鎖,當前線程必須在鎖對象保持同步,把當前線程添加到等待隊列中,隨後另一線程可以同步同一個對象鎖來調用object.notify(),這樣將喚醒原來等待中的線程,然後釋放該鎖。基本上wait()/notify()與sleep()/interrupt()類似,只是前者需要獲取對象鎖。
=============-----------------------------------------------
wait
Wait是Object類的方法,範圍是使該Object實例所處的線程。
Sleep()是Thread類專屬的靜態方法,針對一個特定的線程。
Wait方法使實體所處線程暫停執行,從而使對象進入等待狀態,直到被notify方法通知或者wait的等待的時間到。Sleep方法使持有的線程暫停運行,從而使線程進入休眠狀態,直到用interrupt方法來打斷他的休眠或者sleep的休眠的時間到。Wait方法進入等待狀態時會釋放同步鎖(如上例中的lock對象),而Sleep方法不會釋放同步鎖。所以,當一個線程無限Sleep時又沒有任何人去interrupt它的時候,程序就產生大麻煩了notify是用來通知線程,但在notify之前線程是需要獲得lock的。另個意思就是必須寫在synchronized(lockobj) {...}之中。wait也是這個樣子,一個線程需要釋放某個lock,也是在其獲得lock情況下才能夠釋放,所以wait也需要放在synchronized(lockobj) {...}之中。
Sleep與interrupt
interrupt是個很暴力的方法,打斷一個線程的Sleep時並不需要獲得該線程的lock。雖然暴力卻也有暴力的用處。在一個線程無時限sleep的時候也只有interrupt能夠喚醒他。在interrupt的時候會拋出InterruptedException,這個Exception是由Thread 類自動拋出的。因此Interrupt帶有強烈的阻塞味道。
wait與interrupt
interrupt同樣可以打斷wait的等待,與打斷sleep不同的是,被打斷的wait的線程在重新獲得lock之前是不會拋出InterruptedException。
resume和suspend已經被Java遺棄,因爲他們天生會引起線程的死鎖。
suspend是個貪婪的傢伙,當一個線程在suspend的時候,線程會停下來,但卻仍然持有在這之前獲得的鎖定。其他線程無法使用他鎖定的任何資源,除非這個掛起的線程被resume之後,他纔會繼續運行。對於線程的同步,使用wait與notify要安全的多。
--------------------------------------
sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
獲取對象鎖進入運行狀態。
=============-----------------------------------------------
7、在靜態方法上使用同步時會發生什麼事?
同步靜態方法時會獲取該類的“Class”對象,所以當一個線程進入同步的靜態方法中時,線程監視器獲取類本身的對象鎖,其它線程不能進入這個類的任何靜態同步方法。它不像實例方法,因爲多個線程可以同時訪問不同實例同步實例方法。
8、當一個同步方法已經執行,線程能夠調用對象上的非同步實例方法嗎?
可以,一個非同步方法總是可以被調用而不會有任何問題。實際上,Java沒有爲非同步方法做任何檢查,鎖對象僅僅在同步方法或者同步代碼塊中檢查。如果一個方法沒有聲明爲同步,即使你在使用共享數據Java照樣會調用,而不會做檢查是否安全,所以在這種情況下要特別小心。一個方法是否聲明爲同步取決於臨界區訪問(critial section access),如果方法不訪問臨界區(共享資源或者數據結構)就沒必要聲明爲同步的。
下面有一個示例說明:Common類有兩個方法synchronizedMethod1()和method1(),MyThread類在獨立的線程中調用這兩個方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Common public synchronized void synchronizedMethod1() System.out.println( "synchronizedMethod1 ); try { Thread.sleep( 1000 ); } catch (InterruptedException e.printStackTrace(); } System.out.println( "synchronizedMethod1 ); } public void method1() System.out.println( "Method ); try { Thread.sleep( 1000 ); } catch (InterruptedException e.printStackTrace(); } System.out.println( "Method ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class MyThread extends Thread private int id 0 ; private Common public MyThread(String int no, super (name); common id } public void run() System.out.println( "Running
this .getName()); try { if (id 0 ) common.synchronizedMethod1(); } else { common.method1(); } } catch (Exception e.printStackTrace(); } } public static void main(String[] Common new Common(); MyThread new MyThread( "MyThread-1" , 0 , MyThread new MyThread( "MyThread-2" , 1 , t1.start(); t2.start(); } } |
1 |
|
這裏是程序的輸出:
1 2 3 4 5 6 |
Running 1 synchronizedMethod1 Running 2 Method 1 called synchronizedMethod1 Method 1 done |
結果表明即使synchronizedMethod1()方法執行了,method1()也會被調用。
9、 在一個對象上兩個線程可以調用兩個不同的同步實例方法嗎?
不能,因爲一個對象已經同步了實例方法,線程獲取了對象的對象鎖。所以只有執行完該方法釋放對象鎖後才能執行其它同步方法。看下面代碼示例非常清晰:Common 類 有synchronizedMethod1()和synchronizedMethod2()方法,MyThread調用這兩個方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Common public synchronized void synchronizedMethod1() System.out.println( "synchronizedMethod1 ); try { Thread.sleep( 1000 ); } catch (InterruptedException e.printStackTrace(); } System.out.println( "synchronizedMethod1 ); } public synchronized void synchronizedMethod2() System.out.println( "synchronizedMethod2 ); try { Thread.sleep( 1000 ); } catch (InterruptedException e.printStackTrace(); } System.out.println( "synchronizedMethod2 ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class MyThread extends Thread private int id 0 ; private Common public MyThread(String int no, super (name); common id } public void run() System.out.println( "Running
this .getName()); try { if (id 0 ) common.synchronizedMethod1(); } else { common.synchronizedMethod2(); } } catch (Exception e.printStackTrace(); } } public static void main(String[] Common new Common(); MyThread new MyThread( "MyThread-1" , 0 , MyThread new MyThread( "MyThread-2" , 1 , t1.start(); t2.start(); } } |
10、 什麼是死鎖
死鎖就是兩個或兩個以上的線程被無限的阻塞,線程之間相互等待所需資源。這種情況可能發生在當兩個線程嘗試獲取其它資源的鎖,而每個線程又陷入無限等待其它資源鎖的釋放,除非一個用戶進程被終止。就JavaAPI而言,線程死鎖可能發生在一下情況。
●當兩個線程相互調用Thread.join()
●當兩個線程使用嵌套的同步塊,一個線程佔用了另外一個線程必需的鎖,互相等待時被阻塞就有可能出現死鎖。
11、什麼是線程餓死,什麼是活鎖?
線程餓死和活鎖雖然不想是死鎖一樣的常見問題,但是對於併發編程的設計者來說就像一次邂逅一樣。
當所有線程阻塞,或者由於需要的資源無效而不能處理,不存在非阻塞線程使資源可用。JavaAPI中線程活鎖可能發生在以下情形:
●當所有線程在程序中執行Object.wait(0),參數爲0的wait方法。程序將發生活鎖直到在相應的對象上有線程調用Object.notify()或者Object.notifyAll()。
●當所有線程卡在無限循環中。
這裏的問題並不詳盡,我相信還有很多重要的問題並未提及,您認爲還有哪些問題應該包括在上面呢?歡迎在評論中分享任何形式的問題與建議