ReadMe : 括號里的內容為補充或解釋說明。
多線程和高並發是畢業后求職大廠面試中必問的知識點,自己之前總是面試前才去找相關的知識點面試題來背背,隔段時間又忘了,沒有沉淀下來,於是自己總結了下相關的知識點。
多線程
1. 進程和線程之間有什么不同?
進程是一個獨立的運行環境,它可以被看作是一個程序或者一個應用。而線程是在進程中執行的一個任務。進程是操作系統進行資源分配的基本單位,而線程是操作系統進行調度的基本單位。進程讓操作系統的並發性成為可能,而線程讓進程的內部並發成為可能。好比Java運行環境是一個(包含了不同的類和程序的)單一進程。
想理解的更深刻,請點擊進程與線程的區別.
2. Thread 類中的start() 和run() 方法有什么區別?
1) start()被用來啟動新的線程,run()不能。
2)start()不能被重復調用,run()可以。
3)start()中的run代碼可以不執行完就繼續執行下面的代碼,即線程轉換,如果直接調用run()必須等待其代碼全部執行完才能繼續執行下面的代碼。
4)start()實現了多線程,run()沒有實現多線程。
3. 在多線程中,什么是上下文切換?
上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。是多任務操作系統和多線程環境的基本特征。
4. Java中的volatile 變量是什么?
volatile是一個特殊的修飾符,只有成員變量(類的成員變量、類的靜態成員變量)才能使用它。
被volatile修飾之后就具備了兩層語義:
1)保證了不同線程對這個變量進行操作時的可見性(即一個線程修改了某個變量的值,這新值對其他線程來說是即刻可見的)。
2)禁止進行指令重排序。
5. Java中堆和棧有什么不同?(相對於線程來說)
棧是一塊和線程緊密相關的內存區域。每個線程都有自己的棧內存,用於存儲本地變量,方法參數和棧調用,一個線程中存儲的變量對其它線程是不可見的。
堆是所有線程共享的一片公用內存區域。對象都在堆里創建,為了提升效率線程會從堆中弄一個緩存到自己的棧,如果多個線程使用該變量就可能引發問題,這時volatile 變量就可以發揮作用了,它要求線程從主存中讀取變量的值。
6. 什么是線程池? 為什么要使用它?
創建線程要花費資源和時間,如果任務來了才創建線程那么響應時間會變長,而且一個進程能創建的線程數有限。為了避免這些問題,在程序啟動的時候就創建若干線程來響應處理,它們被稱為線程池,里面的線程叫工作線程。
7. 死鎖是什么?如何避免死鎖?
死鎖是指兩個或兩個以上的進程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。這是一個嚴重的問題,因為死鎖會讓你的程序掛起無法完成任務,死鎖的發生必須滿足以下四個條件:
1)互斥條件:一個資源每次只能被一個進程使用。
2)請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
3)不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
4)循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
避免死鎖最簡單的方法就是阻止循環等待條件,將系統中所有的資源設置標志位、排序,規定所有的進程申請資源必須以一定的順序(升序或降序)做操作來避免死鎖。
8. Thread類中的yield方法有什么作用?
Thread.yield() 方法會使當前線程從運行狀態變為就緒狀態,把運行機會讓給其它相同優先級的線程。它是一個靜態的原生(native)方法而且只保證當前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執行yield()的線程有可能會被再次繼續執行的。
9. Java中notify 和 notifyAll有什么區別?
調用notify時,只有一個等待線程會被喚醒而且它不能保證哪個線程會被喚醒,這取決於線程調度器。雖然如果你調用notifyAll方法,那么等待該鎖的所有線程都會被喚醒。
10. Java中interrupted 和 isInterruptedd方法的區別?
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而后者不會。
Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。
11. Java多線程中調用wait() 和 sleep()方法有什么不同?
sleep()和wait()都是使線程暫停執行一段時間的方法。二者區別為:
1)原理不同。
sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的,它會使此線程暫停執行一段時間,而把執行機會讓給其他線程,等到計時時間一到,此線程會自動蘇醒。而wait()方法是Object類的方法,用於線程間的通信,這個方法會使當前擁有該對象鎖的進程等待,直到其他線程用調用notify()或notifyAll()時才蘇醒過來,開發人員也可以給它指定一個時間使其自動醒來。
2)對鎖的處理機制不同。
由於sleep()方法的主要作用是讓線程暫停一段時間,時間一到則自動恢復,不涉及線程間的通信,因此調用sleep()方法僅僅釋放CPU資源或者讓當前線程停止執行一段時間,但不會釋放鎖。而wait()方法則不同,當調用wait()方法后,線程會釋放掉它所占用的鎖,從而使線程所在對象中的其他synchronized數據可被別的線程使用。
3)使用區域不同。
wait()方法必須放在同步控制方法或者同步語句塊中使用,而sleep方法則可以放在任何地方使用。sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。在sleep的過程中,有可能被其他對象調用它的interrupt(),產生InterruptedException異常。
由於sleep不會釋放鎖標志,容易導致死鎖問題的發生,一般情況下,不推薦使用sleep()方法,而推薦使用wait()方法。
12. 有三個線程T1,T2,T3,怎么確保它們按順序執行?
在多線程中有多種方法讓線程按特定順序執行,你可以用線程類的join()方法在一個線程中啟動另一個線程,另外一個線程完成該線程繼續執行。為了確保三個線程的順序你應該先啟動最后一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最后完成。
13. 如何創建守護線程?
使用Thread類的setDaemon(true)方法可以將線程設置為守護線程,需要注意的是,需要在調用start()方法前調用這個方法,否則會拋出IllegalThreadStateException異常。
/** * @author liao.wenhui * @date 2019/7/15 15:13 */ public class DaemonThread { public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { @Override public void run() { } }); //設置守護線程 daemonThread.setDaemon(true); daemonThread.start(); } }
前提知識:
守護進程(Daemon)是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件(百度百科)。
Java線程分為兩類分別為daemon線程(守護線程)和User線程(用戶線程),在JVM啟動時候會調用main函數,main函數所在的線程是一個用戶線程,這個是我們可以看到的線程,其實JVM內部同時還啟動了好多守護線程,比如垃圾回收線程。那么守護線程和用戶線程有什么區別那?區別之一是當最后一個非守護線程結束時候,JVM會正常退出,而不管當前是否有守護線程,也就是說守護線程是否結束並不影響JVM的退出。言外之意是只要有一個用戶線程還沒結束正常情況下JVM就不會退出。
14. 什么是線程調度器(Thread Scheduler)和時間分片(Time Slicing)?
線程調度器是一個操作系統服務,它負責為Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啟動它,它的執行便依賴於線程調度器的實現。
時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(即最好不要讓你的程序依賴於線程的優先級)。
15.什么是ThreadLocal?
ThreadLocal用於創建線程的本地變量,我們知道一個對象的所有線程會共享它的全局變量,所以這些變量不是線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量。每個線程都會擁有他們自己的Thread變量,它們可以使用get()/set()方法去獲取他們的默認值或者在線程內部改變他們的值。
16.Java線程池中submit() 和 execute()方法有什么區別?
兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
17. Java中Runnable和Callable有什么不同?
Runnable和Callable都代表那些要在不同的線程中執行的任務。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。它們的主要區別是Callable的 call() 方法可以返回任務執行結果值和拋出異常,而Runnable的run()方法沒有這些功能。Callable可以返回裝載有計算結果的Future對象。
高並發
1. 什么是FutureTask?
在Java並發程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包裝,由於FutureTask也是調用了Runnable接口所以它可以提交給Executor來執行。