無論是學習還是編程實踐,你必須掌握的這十個Java多線程問題!

無論是學習還是編程實踐,你必須掌握的這十個Java多線程問題!

一、進程與線程?並行與併發?

進程代表一個運行中的程序,是資源分配與調度的基本單位。進程有三大特性:

1、獨立性:獨立的資源,私有的地址空間,進程間互不影響。

2、動態性:進程具有生命週期。

3、併發性:多進程可以在單核CPU上併發運行。

線程代表進程中的一個順序執行流,多線程就是一個進程中的多個順序執行流。線程也被稱為輕量級的進程,是系統運行的基本單位。

多線程的優勢(進程線程區別):

1、進程之間不能共享內存,線程之間共享內存更容易,多線程可協作完成進程工作;

2、創建進程進行資源分配的代價較創建線程要大得多,所以多線程在高併發環境中效率更高。


並行與併發:

並行是指:多核多CPU或多機器處理同一段處理邏輯的時候,同一時刻多個執行流共同執行。

併發是指:通過CPU的調度算法,使用戶感覺像是同時處理多個任務,但同一時刻只有一個執行流佔用CPU執行。即使多核多CPU環境還是會使用併發,以提高處理效率。

主要的CPU調度算法有如下兩種:

1、分時調度:每個線程輪流獲取CPU使用權,各個線程平均CPU時間片。

2、搶佔式調度:Java虛擬機使用的就是這種調度模型。這種調度方式會根據線程優先級,先調度優先級高的線程,如果線程優先級相同,會隨機選取線程執行。


二、PCB是什麼?

進程控制塊,是進程最重要的數據結構,記錄著進程的標識、調度信息、控制信息、以及處理機狀態。PCB會在進程創建時創建、進程消亡時消亡,伴隨PCB整個生命週期。

其中需要了解的有以下幾點:

1、進程標識符用於唯一的標識一個進程。一個進程通常有兩種標識符:內部標識符和外部標識符。

2、處理機狀態信息主要是由處理機的各種寄存器中的內容組成的。處理機在運行中,許多信息都是放在寄存器中的。當處理機被中斷時,所有的這些信息都保存在PCB中,以便在該進程重新執行時,能從斷點繼續執行。

3、進程調度信息包括進程狀態、進程優先級、進程調度所需的其他信息,事件(阻塞原因)。

4、進程控制信息包括程序和數據的地址、進程同步和通信機制、資源清單、鏈接指針。

5、進程控制塊的組織方式為鏈接方式、索引方式。


三、Java創建線程的四種方式?

1、繼承Thread類,實現run()方法。

2、實現Runnable接口,實現run()方法。Runnable實例對象作為Thread構造方法中的target參數傳入,充當線程執行體。這種方式適用於多個線程共享資源的情況。

3、實現Callable接口,實現call()方法。

call()方法與run()方法的兩個主要區別:

1、call()方法可以有返回值,且返回值類型需要與聲明接口時的泛型類型一致。

2、call()方法可以拋出異常。

使用這種方式,步驟較前兩種方式比較複雜:

1、在聲明接口時就需要指定泛型類型,且call()方法的返回值類型要與泛型類型一致。

2、使用FutureTask封裝Callable實例對象,作為Thread構造方法中的target參數傳入。

3、在調用start()後,可使用FutureTask的get()方法獲取返回值。

4、實現java.util.concurrent.ThreadFactory接口,實現newThread(Runnable r)方法,自定義創建細節。


四、Java終止線程的四種方式?(可參看博客http://blog.csdn.net/zhangliangzi/article/details/52484302)

1、自然終止,run()方法執行結束後,線程自動終止。

2、使用stop()方法,已經被棄用。

3、使用volatile 標誌位(其實就是外部控制的自然死亡)。

4、使用interrupt()方法中斷運行態和阻塞態線程。(關於interrupt(),調用interrupt()方法,立刻改變的是中斷狀態,但如果不是在阻塞態,就不會拋出異常;如果在進入阻塞態後,中斷狀態為已中斷,就會立刻拋出異常。但是在獲取synchronized鎖的過程中不可被中斷。詳解可參看:http://blog.csdn.net/zhangliangzi/article/details/52485319)

注意:當線程拋出一個未被捕獲的異常或錯誤時,線程會異常終止。


五、sleep()方法與wait()方法區別是什麼?wait()方法為什麼屬於Object類?

它們的都是使線程“等待”一段時間,但是:

sleep()方法是Thread類下的方法,控制線程休眠,休眠過程中不會釋放鎖,sleep()時間到後進入就緒態等待調度。

wait()方法是Object類下的方法,控制線程等待,等待過程會釋放鎖,被notify()後會進入就緒態等待調度。

至於wait()方法為什麼屬於Object類,而不是Thread類,是因為:

wait()方法用於多個線程爭用一把鎖(一般為Synchronized鎖住的對象)的情況,同一時刻只有一個線程能夠獲得鎖,其他線程就要在線程隊列等待。作用對象就是被鎖住的對象,所以線程隊列的維護工作應該交給Object。如果交給Thread,那麼每個Thread都要知道其他Thread的狀態,這並不合理。


六、start()方法與run()方法區別?

start()方法用於啟動線程,會把線程狀態由新建態轉為就緒態,為線程分配線程私有的方法棧、程序計數器等資源,而start()方法會自動把run()方法作為線程執行體。

run()方法本身與普通方法並無二致,直接調用run()方法不會具有線程的意義。


七、簡述線程的生命週期

無論是學習還是編程實踐,你必須掌握的這十個Java多線程問題!

1、新建態,通過上述幾種方式創建了具有線程執行體的Thread對象,就進入了新建態。

2、就緒態,調用Thread對象的start()方法,就會為線程分配線程私有的方法棧、程序計數器資源,如果得到CPU資源,線程就會由就緒態轉為運行態。換句話說,就緒態的線程獲得了除CPU之外的所有必須資源。

3、運行態,就緒態線程得到CPU資源就會轉為運行態,執行run()方法。當然,在調用yield()線程讓步的情況,線程會由運行態轉到就緒態,但這個過程可能是及其短暫的,如果當前線程擁有較高的優先級,即使讓步後,它也會直接轉為運行態。

4、阻塞態,會導致阻塞態的方法主要有:sleep()方法、wait()方法、join()方法、等待獲取鎖、等待IO等情況。在這些情況被處理後,就會轉為就緒態,等待調度。

5、終止態,包括第四個問題所說的幾種情況。


八、後臺線程是什麼?有什麼應用?

後臺線程如其名,就是在後臺工作的線程,它的任務是為其他線程提供服務,也叫做“守護線程”與“精靈線程”。通過在start()方法調用前,調用setDeamon(true)方法,將線程設置為後臺線程。後臺線程會在前臺線程死亡後由JVM通知死亡。

後臺線程最大的應用就是垃圾回收線程,它是一個典型的後臺線程。


九、Java常見的鎖類型有哪些?請簡述其特點。

1、synchronized對象同步鎖:synchronized是對對象加鎖,可作用於對象、方法(相當於對this對象加鎖)、靜態方法(相當於對Class實例對象加鎖,鎖住的該類的所有對象)以保證併發環境的線程安全。同一時刻只有一個線程可以獲得鎖。

其底層實現是通過使用對象監視器Monitor,每個對象都有一個監視器,當線程試圖獲取Synchronized鎖定的對象時,就會去請求對象監視器(Monitor.Enter()方法),如果監視器空閒,則請求成功,會獲取執行鎖定代碼的權利;如果監視器已被其他線程持有,線程進入同步隊列等待。

2、Lock同步鎖:與synchronized功能類似,可從Lock與synchronized區別進行分析:

1、Lock可以通過tryLock()方法非阻塞地獲取鎖而。如果獲取了鎖即立刻返回true,否則立刻返回false。這個方法還有加上定時等待的重載方法tryLock(long time, TimeUnit unit)方法,在定時期間內,如果獲取了鎖立刻返回true,否則在定時結束後返回false。在定時等待期間可以被中斷,拋出InterruptException異常。而Synchronized在獲得鎖的過程中是不可被中斷的。

2、Lock可以通過lockInterrupt()方法可中斷的獲取鎖,與lock()方法不同的是等待時可以響應中斷,拋出InterruptException異常。

3、Synchronized是隱式的加鎖解鎖,而Lock必須顯示的加鎖解鎖,而且解鎖應放到finnally中,保證一定會被解鎖,而Synchronized在出現異常時也會自動解鎖。但也因為這樣,Lock更加靈活。

4、Synchronized是JVM層面上的設計,對對象加鎖,基於對象監視器。Lock是代碼實現的。

3、可重入鎖:ReentrantLock與Synchronized都是可重入鎖。可重入意味著,獲得鎖的線程可遞歸的再次獲取鎖。當所有鎖釋放後,其他線程才可以獲取鎖。

4、公平鎖與非公平鎖:“公平性”是指是否等待最久的線程就會獲得資源。如果獲得鎖的順序是順序的,那麼就是公平的。不公平鎖一般效率高於公平鎖。ReentrantLock可以通過構造函數參數控制鎖是否公平。

5、ReentrantReadWriteLock讀寫鎖:是一種非排它鎖, 一般的鎖都是排他鎖,就是同一時刻只有一個線程可以訪問,比如Synchronized和Lock。讀寫鎖就多個線程可以同時獲取讀鎖讀資源,當有寫操作的時候,獲取寫鎖,寫操作之後的讀寫操作都將被阻塞,直到寫鎖釋放。讀寫鎖適合寫操作較多的場景,效率較高。

6、樂觀鎖與悲觀鎖:在Java中的實際應用類並不多,大多用在數據庫鎖上,可參看:http://blog.csdn.net/sdyy321/article/details/6183412

7、死鎖:是當兩個線程互相等待獲取對方的對象監視器時就會發生死鎖。一旦出現死鎖,整個程序既不會出現異常也不會有提示,但所有線程都處於阻塞狀態。死鎖一般出現於多個同步監視器的情況。


十、volatile與automicInteger是什麼?如何使用?

在併發環境中有三個因素需要慎重考量,原子性、可見性、有序性。

volatile主要用於解決可見性,它修飾變量,相當於對當前語句前後加上了“內存柵欄”。使當前代碼之前的代碼不會被重排到當前代碼之後,當前代碼之後的指令不會被重排到當前代碼之前,一定程度保證了有序性。而volatile最主要的作用是使修改volatile修飾的變量值時會使所有線程中的緩存失效,並強制寫入公共主存,保證了各個線程的一致。可以看做是輕量級的Synchronized。詳情可參看:http://www.cnblogs.com/dolphin0520/p/3920373.html。

automicXXX主要用於解決原子性,有一個很經典的問題:i++是原子性的操作碼?答案是不是,它其實是兩步操作,一步是取i的值,一步是++。在取值之後如果有另外的線程去修改這個值,那麼當前線程的i值就是舊數據,會影響最後的運算結果。使用automicXXX就可以非阻塞、保證原子性的對數據進行增減操作。詳情可參看:http://ifeve.com/java-atomic/

注:在此列舉的只是Java多線程最基礎的知識,也是面試官最常問到的,先打牢基礎,再去探討底層原理或者高級用法,除了這十個問題,在此再推薦一些其他的資料:

JVM底層又是如何實現synchronized的:http://www.open-open.com/lib/view/open1352431526366.html

Java線程池詳解:http://blog.csdn.net/zhangliangzi/article/details/52389766

Java線程池深度解析:http://www.cnblogs.com/dolphin0520/p/3932921.html

ConcurrentHashMap原理分析:http://www.cnblogs.com/ITtangtang/p/3948786.html

Java阻塞隊列詳解:http://ifeve.com/java-blocking-queue/



分享到:


相關文章: