一、進程與線程?並行與併發?
進程代表一個運行中的程序,是資源分配與調度的基本單位。進程有三大特性:
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<class>接口,實現call()方法。/<class>
call()方法與run()方法的兩個主要區別:
1、call()方法可以有返回值,且返回值類型需要與聲明接口時的泛型類型一致。
2、call()方法可以拋出異常。
使用這種方式,步驟較前兩種方式比較複雜:
1、在聲明接口時就需要指定泛型類型,且call()方法的返回值類型要與泛型類型一致。
2、使用FutureTask<class>封裝Callable實例對象,作為Thread構造方法中的target參數傳入。/<class>
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()方法不會具有線程的意義。
七、簡述線程的生命週期
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/