若是你即將去一家從事大型系統研發的公司進行Java面試,不可避免的會有多線程相關的問題。下面是一些針對初學者或者新手的問題,若是你已經具有良好的基礎,那麼你能夠跳過本文,直接嘗試針對進階水平的Java多線程編程問題及解答。html
關聯連接: Java multi-threading-1 | Java multi-threading-2java
問題:進程和線程的區別
解答:一個進程對應一個程序的執行,而一個線程則是進程執行過程當中的一個單獨的執行序列,一個進程能夠包含多個線程。線程有時候也被稱爲輕量級進程.mysql
一個Java虛擬機的實例運行在一個單獨的進程中,不一樣的線程共享Java虛擬機進程所屬的堆內存。這也是爲何不一樣的線程能夠訪問同一個對象。線程彼此共享堆內存並保有他們本身獨自的棧空間。這也是爲何當一個線程調用一個方法時,他的局部變量能夠保證線程安全。但堆內存並非線程安全的,必須經過顯示的聲明同步來確保線程安全。面試
問題:列舉幾種不一樣的建立線程的方法.
解答:能夠經過以下幾種方式:
• 繼承Thread 類
• 實現Runnable 接口
• 使用Executor framework (這會建立一個線程池)sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
Counter
extends
Thread {
//method where the thread execution will start
public
void
run(){
//logic to execute in a thread
}
//let’s see how to start the threads
public
static
void
main(String[] args){
Thread t1 =
new
Counter();
Thread t2 =
new
Counter();
t1.start();
//start the first thread. This calls the run() method.
t2.start();
//this starts the 2nd thread. This calls the run() method.
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
Counter
extends
Base
implements
Runnable{
//method where the thread execution will start
public
void
run(){
//logic to execute in a thread
}
//let us see how to start the threads
public
static
void
main(String[] args){
Thread t1 =
new
Thread(
new
Counter());
Thread t2 =
new
Thread(
new
Counter());
t1.start();
//start the first thread. This calls the run() method.
t2.start();
//this starts the 2nd thread. This calls the run() method.
}
}
|
經過線程池來建立更有效率。
相關連接: learn why and how to create pool of threads using the executor framework數據庫
問題:推薦經過哪一種方式建立線程,爲何?
解答:最好使用Runnable接口,這樣你的類就沒必要繼承Thread類,否則當你須要多重繼承的時候,你將束手無策(咱們都知道Java中的類只能繼承自一個類,但能夠同時實現多個接口)。在上面的例子中,由於咱們要繼承Base類,因此實現Runnable接口成了顯而易見的選擇。同時你也要注意到在不一樣的例子中,線程是如何啓動的。按照面向對象的方法論,你應該只在但願改變父類的行爲的時候纔去繼承他。經過實現Runnable接口來代替繼承Thread類能夠告訴使用者Counter是Base類型的一個對象,並會做爲線程執行。編程
問題:簡要的說明一下高級線程狀態.
解答:下圖說明了線程的各類狀態.安全
• 可執行(Runnable):當調用start()方法後,一個線程變爲可執行狀態,可是並不意味着他會馬上開始真正地執行。而是被放入線程池,由線程調度器根據線程優先級決定什麼時候掛起執行。多線程
1
2
|
MyThread aThread =
new
MyThread();
aThread.start();
//becomes runnable
|
• 執行中(Running):處理器已經在執行線程的代碼。他會一直運行直到被阻斷,或者經過靜態方法Thread.yield()自行放棄執行的機會,考慮到場景切換所帶來的開銷,yield()方法不該該被常常調用。
• 等待中(Waiting):線程因爲等待I/O等外部進程的處理結果而處於被阻斷的狀態,調用currObject.wait( )方法會使得當前線程進入等待狀態,直到其它線程調用currObject.notify() 或者currObject.notifyAll() 。
• 睡眠中(Sleeping):重載方法Thread.sleep(milliseconds),Thread.sleep(milliseconds, nanoseconds)能夠迫使Java線程進入睡眠狀態(掛起)。
• 因爲I/O阻塞(Blocked on I/O):當I/O條件發生變化時(例如讀取了幾個字節的數據)會遷移到可執行狀態。
• 因爲同步阻塞中(Blocked on synchronization): 當獲取鎖以後會進入執行中狀態。性能
Thread.State 枚舉類型包含了Java虛擬機支持的所有的線程狀態類型,下面幾點Java的線程宗旨確保了這些線程狀態成爲可能。
• 對象能夠被任何線程共享和修改。
• 線程調度器的搶佔性特性,使得線程能夠隨時在/不在多核處理之間切換處理器內核,這意味着方法能夠在執行的過程當中切換狀態。不然方法中的死循環將永遠阻塞CPU,而且使得不一樣線程的其餘方法始終得不到執行。
• 爲了防止線程安全問題,那些脆弱的方法或者代碼塊能夠被鎖定。這使得線程能夠處於被鎖定或者加鎖請求處理中兩種狀態。
• 線程在處理I/O資源(如Sockets,文件句柄,數據庫鏈接等)時會進入等待狀態,
• 處於I/O讀寫中的線程不能被切換,所以他們或者以成功/失敗的結果正常完成處理,或者其它線程關閉了相應的資源,迫使他進入死亡或者完成的狀態。這也是爲何一個合理的超時時間能夠避免線程因爲I/O處理而被永遠阻塞,從而致使嚴重的性能問題。
• 線程能夠進入睡眠狀態,以使得其餘處於等待狀態的線程有機會執行。
問題:yield和sleeping有何區別,sleep()和wait()有何區別?
解答:當一個任務調用了yield()方法,它將從執行中狀態轉變爲可執行。而當一個任務調用了sleep(),則將從執行中狀態轉變爲等待中/睡眠中狀態。
方法wait(1000)使得當前線程睡眠1秒鐘,但調用notify() 或者notifyAll()會隨時喚醒線程。而sleep(1000)則會致使當前線程休眠1秒鐘。
問題:爲何爲了線程安全而鎖定一個方法或者一個代碼塊稱爲「同步」而不是「鎖定」或者「被鎖定」
解答:當某個方法或者代碼塊被聲明爲」synchronized」後,保存數據的內存空間(例如堆內存)將保持被同步狀態。
這意味着:當一個線程獲取鎖而且執行到已被聲明爲synchronized的方法或者代碼塊時,該線程首先從主堆內存空間中讀取該鎖定對象的全部變化,以確保其在開始執行以前擁有最新的信息。在synchronized部分執行完畢,線程準備釋放鎖的時候,全部針對被鎖定對象的修改都將爲寫入主堆內存中。這樣其餘線程在請求鎖的時候就能夠獲取最新的信息。
問題:線程如何進行的同步處理?你能夠列舉出那些同步級別?同步方法和代碼塊如何區別?
解答:在Java語言中,每一個對象都有一個鎖,一個線程能夠經過關鍵字synchronized來申請獲取某個對象的鎖,關鍵字synchronized能夠被用於方法(粗粒度鎖,對性能影響較大)或代碼塊(細粒度鎖)級別。鎖定方法每每不是一個很好的選擇,取而代之的咱們應該只鎖定那些訪問共享資源的代碼塊,由於每個對象都有一個鎖,因此能夠經過建立虛擬對象來實現代碼塊級別的同步,方法塊級別的鎖比鎖定整個方法更有效。
Java虛擬機靈活的使用鎖和監視器,一個監視器整體來講就是一個守衛者,他負責確保只有一個線程會在同一時間執行被同步的代碼。每一個監視器對應一個對象的引用,在線程執行代碼塊的第一條指令以前,他必須持有該引用對象的鎖,不然他將沒法執行這段代碼。一旦他得到鎖,該線程就能夠進入這段受到保護的代碼。當線程不論以何種方式退出代碼塊時,他都將釋放關聯對象的鎖。對於靜態方法,須要請求類級別的鎖。