多線程知識

多線程在筆試面試中經常出現,下面列出一些公司的多線程筆試面試題。首先是一些概念性的問答題,這些是多線程的基礎知識,經常出現在面試中的第一輪面試(我參加2011年騰訊研究院實習生招聘時就被問到了幾個概念性題目)。然後是一些選擇題,這些一般在筆試時出現,雖然不是太難,但如果在選擇題上花費大多時間無疑會對後面的編程題造成影響,因此必須迅速的解決掉。最後是綜合題即難一些的問答題或是編程題。這種題目當然是最難解決了,要麼會引來面試官的追問,要麼就很容易考慮不周全,因此解決這類題目時一定要考慮全面和細緻。


下面就來看看這三類題目吧。


一.概念性問答題
第一題:線程的基本概念、線程的基本狀態及狀態之間的關係?




第二題:線程與進程的區別?


    這個題目問到的概率相當大,計算機專業考研中也常常考到。要想全部答出比較難。


第三題:多線程有幾種實現方法,都是什麼?


   不論是那種方式,最後都需要通過Thread類的實例調用start()方法來開始線程的執行,start()方法通過java虛擬機調用線程中定義的run方法來執行該線程。通過查看java源程序中的start()方法的定義可以看到,它是通過調用操作系統的start0方法來實現多線程的操作的。


但是一般在系統的開發中遇到多線程的情況的時候,以實現Runnable接口的方式爲主要方式。這是因爲實現接口的方式有很多的優點:


1、就是通過繼承Thread類的方式時,線程類就無法繼承其他的類來實現其他一些功能,實現接口的方式就沒有這中限制;


2.也是最重要的一點就是,通過實現Runnable接口的方式可以達到資源共享的效果。


但是其實兩種方式是有密切聯繫的:


我們通過實現接口Runnable建立的MyThread類,而Thread類也是實現了Runnable接口的子類。如果我們想啓動線程,需要通過Thread類中的start()方法,Thread類中的start()方法來調用MyThread類中run方法來執行該線程。這個實現是典型的代理模式。


java中實現多線程操作有兩種方法:繼承Thread類和實現Runnable接口


一、繼承Thread類


//繼承Thread類


class MyThread extends Thread {


private String name ;


public MyThread(String name) {


this.name = name;


}


public void run() {//覆寫Thread類中的run方法


System.out.println("MyThread-->"+ name);


}


}


public class TestThread {


public static void main(String args[]) {


MyThread t1 = new MyThread("線程1");


MyThread t2 = new MyThread("線程2");


t1.start();//調用線程啓動方法


t2.start();//調用線程啓動方法


}


}


二、實現Runnable接口


class MyThread implements Runnable {


private String name ;


public MyThread(String name) {


this.name = name;


}


public void run() {//覆寫Thread類中的run方法,這是線程的主體


System.out.println("MyThread-->"+ name);


}


}


public class TestThread {


MyThread t = new MyThread("線程");


new Thread(t).start();


new Thread(t).start();


}


第四題:多線程同步和互斥有幾種實現方法,都是什麼? 


四種進程或線程同步互斥的控制方法
1、臨界區:通過對多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問。 
2、互斥量:爲協調共同對一個共享資源的單獨訪問而設計的。 
3、信號量:爲控制一個具有有限數量用戶資源而設計。 
4、事 件:用來通知線程有一些事件已發生,從而啓動後繼任務的開始。
臨界區(Critical Section)(同一個進程內,實現互斥)
保證在某一時刻只有一個線程能訪問數據的簡便辦法。在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區,那麼在有一個線程進入後其他所有試圖訪問此臨界區的線程將被掛起,並一直持續到進入臨界區的線程離開。臨界區在被釋放後,其他線程可以繼續搶佔,並以此達到用原子方式操作共享資源的目的。
互斥量(Mutex)(可以跨進程,實現互斥)
互斥量跟臨界區很相似,只有擁有互斥對象的線程才具有訪問資源的權限,由於互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前佔據資源的線程在任務處理完後應將擁有的互斥對象交出,以便其他線程在獲得後得以訪問資源。互斥量比臨界區複雜。因爲使用互斥不僅僅能夠在同一應用程序不同線程中實現資源的安全共享,而且可以在不同應用程序的線程之間實現對資源的安全共享。 
互斥量與臨界區的作用非常相似,但互斥量是可以命名的,也就是說它可以跨越進程使用。所以創建互斥量需要的資源更多,所以如果只爲了在進程內部是用的話使用臨界區會帶來速度上的優勢並能夠減少資源佔用量。
信號量(Semaphores)(主要是實現同步,可以跨進程)
信號量對象對線程的同步方式與前面幾種方法不同,信號允許多個線程同時使用共享資源,這與操作系統中的PV操作相同。它指出了同時訪問共享資源的線程最大數目。它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目。一般是將當前可用資源計數設置爲最大資源計數,每增加一個線程對共享資源的訪問,當前可用資源計數就會減1,只要當前可用資源計數是大於0的,就可以發出信號量信號。但是當前可用計數減小到0時則說明當前佔用資源的線程數已經達到了所允許的最大數目,不能在允許其他線程的進入,此時的信號量信號將無法發出
事件(Event)(實現同步,可以跨進程)
事件對象也可以通過通知操作的方式來保持線程的同步。並且可以實現不同進程中的線程同步操作。
 


第五題:多線程同步和互斥有何異同,在什麼情況下分別使用他們?舉例說明。
 
二.選擇題
第一題(百度筆試題):


以下多線程對int型變量x的操作,哪幾個不需要進行同步: 
A. x=y;      B. x++;    C. ++x;    D. x=1;


第二題(阿里巴巴筆試題)


多線程中棧與堆是公有的還是私有的


A:棧公有, 堆私有


B:棧公有,堆公有


C:棧私有, 堆公有


D:棧私有,堆私有
同一進程中的線程究竟共享哪些資源
線程共享的環境包括:進程代碼段、進程的公有數據(利用這些共享的數據,線程很容易的實現相互之間的通訊)、進程打開的文件描述符、信號的處理器、進程的當前目錄和進程用戶ID與進程組ID。
    進程擁有這許多共性的同時,還擁有自己的個性。有了這些個性,線程才能實現併發性。這些個性包括:


    1.線程ID
      每個線程都有自己的線程ID,這個ID在本進程中是唯一的。進程用此來標
   識線程。
    2.寄存器組的值
       由於線程間是併發運行的,每個線程有自己不同的運行線索,當從一個線
   程切換到另一個線程上時,必須將原有的線程的寄存器集合的狀態保存,以便
   將來該線程在被重新切換到時能得以恢復。
    3.線程的堆棧
       堆棧是保證線程獨立運行所必須的。
       線程函數可以調用函數,而被調用函數中又是可以層層嵌套的,所以線程
   必須擁有自己的函數堆棧,使得函數調用可以正常執行,不受其他線程的影
   響。


    4.錯誤返回碼
       由於同一個進程中有很多個線程在同時運行,可能某個線程進行系統調用
   後設置了errno值,而在該線程還沒有處理這個錯誤,另外一個線程就在此時
   被調度器投入運行,這樣錯誤值就有可能被修改。
       所以,不同的線程應該擁有自己的錯誤返回碼變量。


    5.線程的信號屏蔽碼
       由於每個線程所感興趣的信號不同,所以線程的信號屏蔽碼應該由線程自
   己管理。但所有的線程都共享同樣的信號處理器。


    6.線程的優先級
       由於線程需要像進程那樣能夠被調度,那麼就必須要有可供調度使用的參
   數,這個參數就是線程的優先級。
涉及多線程程序涉及的時候經常會出現一些令人難以思議的事情,用堆和棧分配一個變量可能在以後的執行中產生意想不到的結果,而這個結果的表現就是內存的非法被訪問,導致內存的內容被更改。 


  理解這個現象的兩個基本概念是:在一個進程的線程共享堆區,而進程中的線程各自維持自己堆棧。 


  另一運行機制就是如果聲明一個成員變量如 char Name[200],隨着這段代碼調用的結束,Name在棧區的地址被釋放,而如果是 char * Name = new char[200]; 情況則完全不同,除非顯示調用delete否則 Name指向的地址不會被釋放。 


  在B中如果用棧區 即採用臨時變量的機制分配聲明V和堆區,而者的結果是不同的。如果用棧區,如果變量地址爲Am1-Am2這麼大,退出B調用時候這段地址被釋放,C函數可能將這段內存改寫;這樣當D執行的時候,從內存Am1-Am2中讀取的內容就是被改過的了。 


  而如果用New(堆)分配,則不會出現那樣的情況,因爲沒有顯示對用delete並且堆對於線程共享,即2線程可以看到1線程在堆裏分配的東西,所以不會發生誤寫。 


  這個問題是筆者在公司實習的時候發現的,因爲當時剛剛涉及多線程程序設計,操作系統中如此簡單的話題困擾筆者很久,希望可以對初涉C++多線程的讀者有所幫助! 2)如果兩個線程共享堆,而且都有可能執行內存分配和釋放操作,就必須進行同步保護,這個和C類,R類,T類沒有關係。你看到的例子兩個線程應該是使用各自的堆。 


  在 windows 等平臺上,不同線程缺省使用同一個堆,所以用 C 的 malloc (或者 windows 的 GlobalAlloc)分配內存的時候是使用了同步保護的。如果沒有同步保護,在兩個線程同時執行內存操作的時候會產生競爭條件,可能導致堆內內存管理混亂。比如兩個線程分配了統一塊內存地址,空閒鏈表指針錯誤等。 


  Symbian 的線程一般使用獨立的堆空間。這樣每個線程可以直接在自己的堆裏分配和釋放,可以減少同步所引入的開銷。當線程退出的時候,系統直接回收線程的堆空間,線程內沒有釋放的內存空間也不會造成進程內的內存泄漏。 


  但是兩個線程使用共用堆的時候,就必須用 critical section 或者 mutex 進行同步保護。否則程序崩潰時早晚的事。如果你的線程需要在共用堆上無規則的分配和釋放任何數量和類型的對象,可以定製一個自己的 allcator,在 allocator 內部使用同步保護。線程直接使用這個 allocator 分配內存就可以了。這相當於實現自己的 malloc,free。但是更建議你重新審查一下自己的系統,因爲這種情況大多數是不必要的。經過良好的設計,線程的本地堆應該能夠滿足大多數對象的需求。如果有某一類對象需要在共享堆上創建和共享,這種需求是比較合理的,可以在這個類的 new 和 delete 上實現共享保護。 


三.綜合題
第一題(臺灣某殺毒軟件公司面試題):


在Windows編程中互斥量與臨界區比較類似,請分析一下二者的主要區別。


 


第二題:


一個全局變量tally,兩個線程併發執行(代碼段都是ThreadProc),問兩個線程都結束後,tally取值範圍。


inttally = 0;//glable


voidThreadProc()


{


       for(inti = 1; i <= 50; i++)


              tally += 1;


}


 


第三題(某培訓機構的練習題):


子線程循環 10 次,接着主線程循環 100 次,接着又回到子線程循環 10 次,接着再回到主線程又循環 100 次,如此循環50次,試寫出代碼。


 


第四題(迅雷筆試題):


編寫一個程序,開啓3個線程,這3個線程的ID分別爲A、B、C,每個線程將自己的ID在屏幕上打印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC….依次遞推。


 


第五題(Google面試題)


有四個線程1、2、3、4。線程1的功能就是輸出1,線程2的功能就是輸出2,以此類推.........現在有四個文件ABCD。初始都爲空。現要讓四個文件呈如下格式:


A:1 2 3 4 1 2....


B:2 3 4 1 2 3....


C:3 4 1 2 3 4....


D:4 1 2 3 4 1....


請設計程序。


 


下面的第六題與第七題也是在考研中或是程序員和軟件設計師認證考試中的熱門試題。


第六題


生產者消費者問題


這是一個非常經典的多線程題目,題目大意如下:有一個生產者在生產產品,這些產品將提供給若干個消費者去消費,爲了使生產者和消費者能併發執行,在兩者之間設置一個有多個緩衝區的緩衝池,生產者將它生產的產品放入一個緩衝區中,消費者可以從緩衝區中取走產品進行消費,所有生產者和消費者都是異步方式運行的,但它們必須保持同步,即不允許消費者到一個空的緩衝區中取產品,也不允許生產者向一個已經裝滿產品且尚未被取走的緩衝區中投放產品。


 


第七題


讀者寫者問題


這也是一個非常經典的多線程題目,題目大意如下:有一個寫者很多讀者,多個讀者可以同時讀文件,但寫者在寫文件時不允許有讀者在讀文件,同樣有讀者讀時寫者也不能寫
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章