並行和並發有什麼差別? (建議學習:java常見面試題)
並行是指兩個或多個事件在同一時刻發生;而並發是指兩個或多個事件在同一時刻同一時刻發生;時間間隔發生。
並行是在不同實體上的多個事件,並發是在同一實體上的多個事件。
在一台處理器上「同時」處理多個任務,在多台處理器上同時處理多個任務。如hadoop分散式集群。
所以並發程式設計的目標是充分的利用處理器的每一個核,以達到最高的處理效能。
執行緒和行程的差別?
簡而言之,進程是程式運作和資源分配的基本單位,一個程式至少有一個行程,一個行程至少有一個執行緒。進程在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體資源,減少切換次數,進而效率更高。
執行緒是進程的一個實體,是cpu調度和分派的基本單位,是比程式更小的能獨立運作的基本單位。同一進程中的多個執行緒之間可以並發執行。
守護線程是什麼?
守護線程(即daemon thread),是個服務線程,準確地說就是服務其他的線程。
建立執行緒有哪幾種方式?
①. 繼承Thread類別建立執行緒類別
定義Thread類別的子類,並重寫該類別的run方法,該run方法的方法體就代表了執行緒要完成的任務。因此把run()方法稱為執行體。
建立Thread子類別的實例,即建立了執行緒物件。
呼叫執行緒物件的start()方法來啟動該執行緒。
②. 透過Runnable介面建立執行緒類別
#定義runnable介面的實作類,並重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的線程執行體。
建立 Runnable實作類別的實例,並依此實例作為Thread的target來建立Thread對象,該Thread對象才是真正的執行緒對象。
呼叫執行緒物件的start()方法來啟動該執行緒。
③. 透過Callable和Future建立執行緒
建立Callable介面的實作類,並實作call()方法,該call()方法將作為執行緒執行體,並且有傳回值。
建立Callable實作類別的實例,使用FutureTask類別來包裝Callable對象,該FutureTask物件封裝了該Callable物件的call()方法的傳回值。
使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。
呼叫FutureTask物件的get()方法來取得子執行緒執行結束後的回傳值。
說一下 runnable 和 callable 有什麼不同?
有點深的問題了,也看出一個Java程式設計師學習知識的廣度。
Runnable介面中的run()方法的回傳值是void,它所做的事情只是純粹地去執行run()方法中的程式碼而已;
Callable介面中的call( )方法是有回傳值的,是一個泛型,和Future、FutureTask配合可以用來得到非同步執行的結果。
執行緒有哪些狀態?
執行緒通常都有五種狀態,建立、就緒、執行、阻塞和死亡。
建立狀態。在產生線程對象,並沒有呼叫該對象的start方法,這是線程處於創建狀態。
就緒狀態。當呼叫了線程物件的start方法之後,該線程就進入了就緒狀態,但是此時線程調度程序還沒有把該線程設定為當前線程,此時處於就緒狀態。在執行緒運行之後,從等待或睡眠回來之後,也會處於就緒狀態。
運行狀態。線程調度程序將處於就緒狀態的線程設定為當前線程,此時線程就進入了運行狀態,開始運行run函數當中的程式碼。
阻塞狀態。執行緒正在運作的時候,被暫停,通常是為了等待某個時間的發生(比如說某項資源就緒)之後再繼續運作。 sleep,suspend,wait等方法都可以導致執行緒阻塞。
死亡狀態。如果一個執行緒的run方法執行結束或呼叫stop方法後,該執行緒就會死亡。對於已經死亡的線程,無法再使用start方法令其進入就緒
sleep() 和 wait() 有什麼區別?
sleep():方法是線程類別(Thread)的靜態方法,讓呼叫線程進入睡眠狀態,讓出執行機會給其他線程,等到休眠時間結束後,線程進入就緒狀態和其他執行緒一起競爭cpu的執行時間。
因為sleep() 是static靜態的方法,他不能改變物件的機鎖,當一個synchronized區塊中呼叫了sleep() 方法,線程雖然進入休眠,但是物件的機鎖沒有被釋放,其他執行緒依然無法存取這個物件。
wait():wait()是Object類別的方法,當一個執行緒執行到wait方法時,它就進入一個和該物件相關的等待池,同時釋放物件的機鎖,使得其他執行緒能夠訪問,可以透過notify,notifyAll方法來喚醒等待的線程
notify()和notifyAll()有什麼區別?
如果執行緒呼叫了物件的 wait()方法,那麼執行緒就會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖定。
當有執行緒呼叫了物件的notifyAll()方法(喚醒所有wait 執行緒)或notify()方法(只隨機喚醒一個wait 執行緒),被喚醒的執行緒就會進入該物件的鎖定池中,鎖定池中的執行緒會去競爭該物件鎖定。
也就是說,呼叫了notify後只要一個執行緒會由等待池進入鎖定池,而notifyAll會將該物件等待池內的所有執行緒移到鎖定池中,等待鎖定競爭。
優先順序高的執行緒競爭到物件鎖的機率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖定池中,只有執行緒再次呼叫wait()方法,它才會重新回到等待池中。
而競爭到物件鎖定的執行緒則繼續往下執行,直到執行了 synchronized 程式碼區塊,它會釋放掉該物件鎖定,這時鎖定池中的執行緒會繼續競爭該物件鎖定。
線程的 run()和 start()有什麼差別?
每個執行緒都是透過某個特定Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。透過呼叫Thread類別的start()方法來啟動一個執行緒。
start()方法來啟動一個線程,真正實作了多執行緒運行。這時無需等待run方法體程式碼執行完畢,可以直接繼續執行下面的程式碼; 這時此執行緒是處於就緒狀態, 並沒有執行。
然後透過此Thread類別呼叫方法run()來完成其運行狀態, 這裡方法run()稱為線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。然後CPU再調度其它線程。
run()方法是在本執行緒裡的,只是執行緒裡的一個函數,而不是多執行緒的。如果直接呼叫run(),其實就相當於是呼叫了一個普通函數而已,直接待用run()方法必須等待run()方法執行完畢才能執行下面的程式碼,所以執行路徑還是只有一條,根本就沒有線程的特徵,所以在多執行緒執行時要使用start()方法而不是run()方法。
建立線程池有哪幾種方式?
①. newFixedThreadPool(int nThreads)
建立一個固定長度的線程池,每當提交一個任務就建立一個線程,直到達到線程池的最大數量,這時線程規模將不再變化,當線程發生未預期的錯誤而結束時,線程池會補充一個新的線程。
②. newCachedThreadPool()
建立一個可快取的線程池,如果線程池的規模超過了處理需求,將自動回收空閒線程,而當需求增加時,則可以自動新增線程,線程池的規模不存在任何限制。
③. newSingleThreadExecutor()
這是一個單執行緒的Executor,它建立單一工作執行緒來執行任務,如果這個執行緒異常結束,會建立一個新的來替代它;它的特點是能確保依照任務在佇列中的順序來串列執行。
④. newScheduledThreadPool(int corePoolSize)
建立了一個固定長度的執行緒池,而且以延遲或定時的方式來執行任務,類似於Timer。
執行緒池都有哪些狀態?
執行緒池有5種狀態:Running、ShutDown、Stop、Tidying、Terminated。
執行緒池各個狀態切換框架圖:
#執行緒池中submit()和execute()方法有什麼區別?
接收的參數不一樣
submit有回傳值,而execute沒有
submit方便Exception處理
在java 程序中怎麼保證多執行緒的運作安全?
線程安全在三個方面體現:
原子性:提供互斥訪問,同一時刻只能有一個線程對資料進行操作,( atomic,synchronized);
可見性:一個線程對主記憶體的修改可以及時地被其他線程看到,(synchronized,volatile);
有序性:一個線程觀察其他執行緒中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序,(happens-before原則)。
多執行緒鎖的升級原理是什麼?
在Java中,鎖共有4種狀態,等級從低到高依序為:無狀態鎖,偏向鎖,輕量級鎖和重量級鎖狀態,這幾個狀態會隨著競爭狀況逐漸升級。鎖可以升級但不能降級。
鎖定升級的圖示流程:
#什麼是死鎖?
死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。
是作業系統層面的錯誤,是進程死鎖的簡稱,最早在1965 年由Dijkstra 在研究銀行家演算法時提出的,它是電腦作業系統乃至整個並發程式設計領域最難處理的問題之一。
怎麼防止死鎖?
死鎖的四個必要條件:
互斥條件:進程對所指派的資源不允許其他行程進行訪問,若其他行程存取該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源
請求和保持條件:進程獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他進程佔有,此事請求阻塞,但又對自己獲得的資源保持不放
不可剝奪條件:是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完後自己釋放
環路等待條件:是指進程發生死鎖後,若干進程之間形成一種頭尾相接的循環等待資源關係
這四個條件是死鎖的必要條件,只要係統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。
了解死鎖的原因,特別是產生死鎖的四個必要條件,就可以最大可能地避免、預防和 解除死鎖。
所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配演算法,避免進程永久佔據系統資源。
此外,也要防止行程在等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃。
ThreadLocal 是什麼?有哪些使用場景?
執行緒局部變數是侷限於執行緒內部的變量,屬於執行緒本身所有,不在多個執行緒間共用。 Java提供ThreadLocal類別來支援線程局部變量,是一種實現線程安全的方式。
但是在管理環境下(如 web 伺服器)使用執行緒局部變數的時候要特別小心,在這種情況下,工作執行緒的生命週期比任何應用變數的生命週期都要長。
任何執行緒局部變數一旦在工作完成後沒有釋放,Java 應用程式就存在記憶體外洩的風險。
說一下 synchronized 底層實作原理?
synchronized可以保證方法或程式碼區塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性。
Java中每個物件都可以當作鎖,這是synchronized實作同步的基礎:
普通同步方法,鎖定是目前實例物件
#靜態同步方法,鎖是目前類別的class物件
同步方法區塊,鎖是括號裡面的物件
synchronized 和volatile 的差別是什麼?
volatile本質是在告訴jvm目前變數在暫存器(工作記憶體)中的值是不確定的,需要從主記憶體讀取;synchronized 則是鎖定目前變量,只有目前執行緒可以訪問該變量,其他線程被阻塞住。
volatile僅能使用在變數層級;synchronized則可以使用在變數、方法、和類別層級的。
volatile僅能實現變數的修改可見性,不能保證原子性;而synchronized則可以保證變數的修改可見性和原子性。
volatile不會造成執行緒的阻塞;synchronized可能會造成執行緒的阻塞。
volatile標記的變數不會被編譯器最佳化;synchronized標記的變數可以被編譯器最佳化。
synchronized 和 Lock 有什麼不同?
首先synchronized是java內建關鍵字,在jvm層面,Lock是個java類別;
synchronized無法判斷是否取得鎖定的狀態,Lock可以判斷是否取得到鎖定;
synchronized會自動釋放鎖定(a 執行緒執行完同步程式碼會釋放鎖定;b 執行緒執行過程中發生異常會釋放鎖定),Lock需在finally中手動釋放鎖定(unlock()方法釋放鎖定),否則容易造成線程死鎖;
用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,則線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可);
Lock鎖適合大量同步的程式碼的同步問題,synchronized鎖適合程式碼少量的同步問題。
synchronized 和 ReentrantLock 差別是什麼?
synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質差異。
既然ReentrantLock是類,那麼它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:
ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
ReentrantLock可以獲取各種鎖的資訊
ReentrantLock可以靈活地實現多路通知
另外,二者的鎖定機制其實也是不一樣的:ReentrantLock底層呼叫的是Unsafe的park方法加鎖,synchronized操作的應該是物件頭中mark word。
說一下 atomic 的原理?
Atomic套件中的類別基本的特性就是在多執行緒環境下,當有多個執行緒同時對單一(包括基本型別及參考型別)變數進行操作時,具有排他性,即當當多個執行緒同時對該變數的值進行更新時,僅有一個執行緒能成功,而未成功的執行緒可以向自旋鎖一樣,繼續嘗試,一直等到執行成功。
Atomic系列的類別中的核心方法都會呼叫unsafe類別中的幾個本地方法。我們需要先知道一個東西就是Unsafe類,全名為:sun.misc.Unsafe,這個類包含了大量的對C代碼的操作,包括很多直接內存分配以及原子操作的調用,而它之所以標記為非安全的,是告訴你這個裡面大量的方法調用都會存在安全隱患,需要小心使用,否則會導致嚴重的後果,例如在通過unsafe分配內存的時候,如果自己指定某些區域可能會導致一些類似C 一樣的指標越界到其他進程的問題。
以上是java的多執行緒常見面試題的詳細內容。更多資訊請關注PHP中文網其他相關文章!