- 相關(guān)推薦
常見(jiàn)的Java多線程面試問(wèn)題及解答
問(wèn)題:進(jìn)程和線程的區(qū)別
解答:一個(gè)進(jìn)程對(duì)應(yīng)一個(gè)程序的執(zhí)行,而一個(gè)線程則是進(jìn)程執(zhí)行過(guò)程中的一個(gè)單獨(dú)的執(zhí)行序列,一個(gè)進(jìn)程可以包含多個(gè)線程。線程有時(shí)候也被稱(chēng)為輕量級(jí)進(jìn)程.
一個(gè)Java虛擬機(jī)的實(shí)例運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中,不同的線程共享Java虛擬機(jī)進(jìn)程所屬的堆內(nèi)存。這也是為什么不同的線程可以訪問(wèn)同一個(gè)對(duì)象。線 程彼此共享堆內(nèi)存并保有他們自己獨(dú)自的?臻g。這也是為什么當(dāng)一個(gè)線程調(diào)用一個(gè)方法時(shí),他的局部變量可以保證線程安全。但堆內(nèi)存并不是線程安全的,必須通 過(guò)顯示的聲明同步來(lái)確保線程安全。
問(wèn)題:列舉幾種不同的創(chuàng)建線程的方法.
解答:可以通過(guò)如下幾種方式:
繼承Thread 類(lèi)
實(shí)現(xiàn)Runnable 接口
使用Executor framework (這會(huì)創(chuàng)建一個(gè)線程池)
123456789101112131415class 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 threadspublic 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. }}123456789101112131415class 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 threadspublic 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. }}
通過(guò)線程池來(lái)創(chuàng)建更有效率。
問(wèn)題:推薦通過(guò)哪種方式創(chuàng)建線程,為什么?
解答:最好使用Runnable接口,這樣你的類(lèi)就不必繼承Thread類(lèi),不然當(dāng)你需要多重繼承的時(shí)候,你將 一籌莫展(我們都知道Java中的類(lèi)只能繼承自一個(gè)類(lèi),但可以同時(shí)實(shí)現(xiàn)多個(gè)接口)。在上面的例子中,因?yàn)槲覀円^承Base類(lèi),所以實(shí)現(xiàn)Runnable 接口成了顯而易見(jiàn)的選擇。同時(shí)你也要注意到在不同的例子中,線程是如何啟動(dòng)的。按照面向?qū)ο蟮姆椒ㄕ,你?yīng)該只在希望改變父類(lèi)的行為的時(shí)候才去繼承他。通 過(guò)實(shí)現(xiàn)Runnable接口來(lái)代替繼承Thread類(lèi)可以告訴使用者Counter是Base類(lèi)型的一個(gè)對(duì)象,并會(huì)作為線程執(zhí)行。
問(wèn)題:簡(jiǎn)要的說(shuō)明一下高級(jí)線程狀態(tài).
解答:下圖說(shuō)明了線程的各種狀態(tài).
可執(zhí)行(Runnable):當(dāng)調(diào)用start()方法后,一個(gè)線程變?yōu)榭蓤?zhí)行狀態(tài),但是并不意味著他會(huì)立刻開(kāi)始真正地執(zhí)行。而是被放入線程池,
由線程調(diào)度器根據(jù)線程優(yōu)先級(jí)決定何時(shí)掛起執(zhí)行。
12MyThread aThread = new MyThread();aThread.start(); //becomes runnable
執(zhí)行中(Running):處理器已經(jīng)在執(zhí)行線程的代碼。他會(huì)一直運(yùn)行直到被阻斷,或者通過(guò)靜態(tài)方法Thread.yield()自行放棄執(zhí)行的機(jī)會(huì),考慮到場(chǎng)景切換所帶來(lái)的開(kāi)銷(xiāo),yield()方法不應(yīng)該被經(jīng)常調(diào)用。
等待中(Waiting):線程由于等待I/O等外部進(jìn)程的處理結(jié)果而處于被阻斷的狀態(tài),調(diào)用currObject.wait( )方法會(huì)使得當(dāng)前線程進(jìn)入等待狀態(tài),直到其它線程調(diào)用currObject.notify() 或者currObject.notifyAll() 。
睡眠中(Sleeping):重載方法Thread.sleep(milliseconds),Thread.sleep(milliseconds, nanoseconds)可以迫使Java線程進(jìn)入睡眠狀態(tài)(掛起)。
由于I/O阻塞(Blocked on I/O):當(dāng)I/O條件發(fā)生變化時(shí)(例如讀取了幾個(gè)字節(jié)的數(shù)據(jù))會(huì)遷移到可執(zhí)行狀態(tài)。
由于同步阻塞中(Blocked on synchronization): 當(dāng)獲取鎖之后會(huì)進(jìn)入執(zhí)行中狀態(tài)。
Thread.State 枚舉類(lèi)型包含了Java虛擬機(jī)支持的全部的線程狀態(tài)類(lèi)型,下面幾點(diǎn)Java的線程宗旨確保了這些線程狀態(tài)成為可能。
對(duì)象可以被任何線程共享和修改。
線程調(diào)度器的搶占性特性,使得線程可以隨時(shí)在/不在多核處理之間切換處理器內(nèi)核,這意味著方法可以在執(zhí)行的過(guò)程中切換狀態(tài)。否則方法中的死循環(huán)將永遠(yuǎn)阻塞CPU,并且使得不同線程的其他方法始終得不到執(zhí)行。
為了防止線程安全問(wèn)題,那些脆弱的方法或者代碼塊可以被鎖定。這使得線程可以處于被鎖定或者加鎖請(qǐng)求處理中兩種狀態(tài)。
線程在處理I/O資源(如Sockets,文件句柄,數(shù)據(jù)庫(kù)連接等)時(shí)會(huì)進(jìn)入等待狀態(tài),
處于I/O讀寫(xiě)中的線程不能被切換,因此他們或者以成功/失敗的結(jié)果正常完成處理,或者其它線程關(guān)閉了相應(yīng)的資源,迫使他進(jìn)入死亡或者完成的狀態(tài)。這也是為什么一個(gè)合理的超時(shí)時(shí)間可以避免線程由于I/O處理而被永遠(yuǎn)阻塞,從而導(dǎo)致嚴(yán)重的性能問(wèn)題。
線程可以進(jìn)入睡眠狀態(tài),以使得其他處于等待狀態(tài)的線程有機(jī)會(huì)執(zhí)行。
問(wèn)題:yield和sleeping有何區(qū)別,sleep()和wait()有何區(qū)別?
解答:當(dāng)一個(gè)任務(wù)調(diào)用了yield()方法,它將從執(zhí)行中狀態(tài)轉(zhuǎn)變?yōu)榭蓤?zhí)行。而當(dāng)一個(gè)任務(wù)調(diào)用了sleep(),則將從執(zhí)行中狀態(tài)轉(zhuǎn)變?yōu)榈却?睡眠中狀態(tài)。
方法wait(1000)使得當(dāng)前線程睡眠1秒鐘,但調(diào)用notify() 或者notifyAll()會(huì)隨時(shí)喚醒線程。而sleep(1000)則會(huì)導(dǎo)致當(dāng)前線程休眠1秒鐘。
問(wèn)題:為什么為了線程安全而鎖定一個(gè)方法或者一個(gè)代碼塊稱(chēng)為“同步”而不是“鎖定”或者“被鎖定”
解答:當(dāng)某個(gè)方法或者代碼塊被聲明為”synchronized”后,保存數(shù)據(jù)的內(nèi)存空間(例如堆內(nèi)存)將保持被同步狀態(tài)。
這意味著:當(dāng)一個(gè)線程獲取鎖并且執(zhí)行到已被聲明為synchronized的方法或者代碼塊時(shí),該線程首先從主堆內(nèi)存空間中讀取該鎖定對(duì)象的所有變 化,以確保其在開(kāi)始執(zhí)行之前擁有最新的信息。在synchronized部分執(zhí)行完畢,線程準(zhǔn)備釋放鎖的時(shí)候,所有針對(duì)被鎖定對(duì)象的修改都將為寫(xiě)入主堆內(nèi) 存中。這樣其他線程在請(qǐng)求鎖的時(shí)候就可以獲取最新的信息。
問(wèn)題:線程如何進(jìn)行的同步處理?你可以列舉出那些同步級(jí)別?同步方法和代碼塊如何區(qū)別?
解答:在Java語(yǔ)言中,每個(gè)對(duì)象都有一個(gè)鎖,一個(gè)線程可以通過(guò)關(guān)鍵字synchronized來(lái)申請(qǐng)獲取某個(gè) 對(duì)象的鎖,關(guān)鍵字synchronized可以被用于方法(粗粒度鎖,對(duì)性能影響較大)或代碼塊(細(xì)粒度鎖)級(jí)別。鎖定方法往往不是一個(gè)很好的選擇,取而 代之的我們應(yīng)該只鎖定那些訪問(wèn)共享資源的代碼塊,因?yàn)槊恳粋(gè)對(duì)象都有一個(gè)鎖,所以可以通過(guò)創(chuàng)建虛擬對(duì)象來(lái)實(shí)現(xiàn)代碼塊級(jí)別的同步,方法塊級(jí)別的鎖比鎖定整個(gè) 方法更有效。
Java虛擬機(jī)靈活的使用鎖和監(jiān)視器,一個(gè)監(jiān)視器總體來(lái)說(shuō)就是一個(gè)守衛(wèi)者,他負(fù)責(zé)確保只有一個(gè)線程會(huì)在同一時(shí)間執(zhí)行被同步的代碼。每個(gè)監(jiān)視器對(duì)應(yīng)一 個(gè)對(duì)象的引用,在線程執(zhí)行代碼塊的第一條指令之前,他必須持有該引用對(duì)象的鎖,否則他將無(wú)法執(zhí)行這段代碼。一旦他獲得鎖,該線程就可以進(jìn)入這段受到保護(hù)的 代碼。當(dāng)線程不論以何種方式退出代碼塊時(shí),他都將釋放關(guān)聯(lián)對(duì)象的鎖。對(duì)于靜態(tài)方法,需要請(qǐng)求類(lèi)級(jí)別的鎖。
【常見(jiàn)的Java多線程面試問(wèn)題及解答】相關(guān)文章:
應(yīng)屆面試常見(jiàn)問(wèn)題及解答10-24
護(hù)士面試常見(jiàn)問(wèn)題及解答07-08
面試常見(jiàn)問(wèn)題解答03-19