java多執行緒面試中常見知識點
1.程序和執行緒
(1)程序是資源分配的最小單位,執行緒是程式執行的最小單位。
(2)程序有自己的獨立地址空間,每啟動一個程序,系統就會為它分配地址空間,建立資料表來維護程式碼段、堆疊段和資料段,這種操作非常昂貴。而執行緒是共享程序中的資料的,使用相同的地址空間,因此CPU切換一個執行緒的花費遠比程序要小很多,同時建立一個執行緒的開銷也比程序要小很多。
(3)執行緒之間的通訊更方便,同一程序下的執行緒共享全域性變數、靜態變數等資料,而程序之間的通訊需要以通訊的方式(IPC)進行。不過如何處理好同步與互斥是編寫多執行緒程式的難點。
(4)但是多程序程式更健壯,多執行緒程式只要有一個執行緒死掉,整個程序也死掉了,而一個程序死掉並不會對另外一個程序造成影響,因為程序有自己獨立的地址空間。
(5)執行緒只由堆疊暫存器,程式計數器,TCB(執行緒控制塊)組成。
2.Thread中start和run方法的區別
(1) 呼叫start()方法會建立一個新的子執行緒並啟動。
(2) run()方法只是Thread的一個普通方法的呼叫。
3.如何給run()方法傳參
(1)建構函式;
(2)成員變數(set和get);
(3)回撥函式。
4.如何實現處理執行緒的返回值?
(1)主執行緒等待法;
(2)使用Thread類的join()阻塞當前執行緒以等待子執行緒處理完畢;(缺點:粒度不夠細);
(3)通過Callable介面實現:通過FutureTask OR 執行緒池獲取。
5.sleep()和wait()的區別?
(1)sleep是Thread類的方法,wait是Object類中定義的方法(native);
(2)sleep方法可以在任何地方使用;
(3)wait方法只能在syn方法或同步快中使用;
(4)**sleep只會讓出cpu,不會導致鎖行為的改變;
(5)**wait不僅讓出cpu,還會釋放已經佔有的同步資源。
6.Java中的鎖池和等待池
Java平臺中,因為有內建鎖的機制,每個物件都可以承擔鎖的功能。Java虛擬機器會為每個物件維護兩個“佇列”(姑且稱之為“佇列”,儘管它不一定符合資料結構上佇列的“先進先出”原則):一個叫Entry Set(入口集),另外一個叫Wait Set(等待集)。對於任意的物件objectX,objectX的Entry Set用於儲存等待獲取objectX這個鎖的所有執行緒,也就是傳說中的鎖池,objectX的Wait Set用於儲存執行了objectX.wait()/wait(long)的執行緒,也就是等待池。
7.notify()和notifyAll()的區別?
notify 僅僅通知一個執行緒使其進入鎖池,但我們不知道哪個執行緒會收到通知,然而 notifyAll 會通知所有等待中的執行緒。
8.yield
Thread.yield()方法作用是:暫停當前正在執行的執行緒物件(及放棄當前擁有的cup資源),並執行其他執行緒。
yield()做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。
9.java中interrupt(),isInterrupted()和interrupted()
(1)當對一個執行緒,呼叫 interrupt() 時:
① 如果執行緒處於正常活動狀態,那麼會將該執行緒的中斷標誌設定為 true。被設定中斷標誌的執行緒將繼續正常執行,不受影響。
② 如果執行緒處於被阻塞狀態(sleep, wait, join 等狀態),那麼執行緒將立即退出被阻塞狀態,並丟擲一個InterruptedException
interrupt()並不能真正的中斷執行緒,需要被呼叫的執行緒自己進行配合才行
① 在正常執行任務時,經常檢查本執行緒的中斷標誌位,如果被設定了中斷標誌就自行停止執行緒
② 在呼叫阻塞方法時正確處理InterruptedException異常
(2)interrupted()是靜態方法:其作用是測試當前執行緒是否已經中斷,內部實現是呼叫的當前執行緒的isInterrupted(boolean ClearInterrupted),並且會清除當前執行緒的中斷狀態。(ClearInterrupted引數可以用來控制是否清除中斷標誌)
(3)isInterrupted()是例項方法:isInterrupted()方法用來測試呼叫該方法的執行緒的中斷狀態,不會清除當前執行緒的中斷狀態。
10. CAS(Compare-and-Swap)
通常提到併發程式設計,在資料的一致性上面,都會考慮用加鎖方式在解決。在這裡,對鎖的解決方案就不多提了,其優勢在於程式設計簡單,java裡面解決方案很多,synchronized關鍵字或者Lock都能提供原子化操作語意。但其劣勢也非常明顯,加鎖其實並不耗時間,但是其容易造成資源等待,等待是非常耗時的,不合理的設計還容易造成死鎖。更好的辦法就是利用CAS機制實現樂觀鎖(每次不加鎖去完成操作,如果因為衝突失敗就重試,直到成功。)來解決效率問題。
CAS是由CPU硬體實現,所以執行相當快.CAS有三個操作引數:記憶體地址,期望值,要修改的新值,當期望值和記憶體當中的值進行比較不相等的時候,表示記憶體中的值已經被別線程改動過,這時候失敗返回,當相等的時候,將記憶體中的值改為新的值,並返回成功。
11. volatile
volatile是Java提供的一種輕量級的同步機制。Java 語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變數,相比於synchronized(synchronized通常稱為重量級鎖),volatile更輕量級,因為它不會引起執行緒上下文的切換和排程。但是volatile 變數的同步性較差(有時它更簡單並且開銷更低),而且其使用也更容易出錯。
volatile變數的特性:
(1)保證可見性,不保證原子性
a.當寫一個volatile變數時,JMM會把該執行緒本地記憶體中的變數強制重新整理到主記憶體中去;
b.這個寫會操作會導致其他執行緒中的快取無效。
(2)禁止指令重排重排序是指編譯器和處理器為了優化程式效能而對指令序列進行排序的一種手段。重排序需要遵守一定規則:
a.重排序操作不會對存在資料依賴關係的操作進行重排序。
比如:a=1;b=a; 這個指令序列,由於第二個操作依賴於第一個操作,所以在編譯時和處理器執行時這兩個操作不會被重排序。
b.重排序是為了優化效能,但是不管怎麼重排序,單執行緒下程式的執行結果不能被改變
比如:a=1;b=2;c=a+b這三個操作,第一步(a=1)和第二步(b=2)由於不存在資料依賴關係, 所以可能會發 生重排序,但是c=a+b這個操作是不會被重排序的,因為需要保證最終的結果一定是c=a+b=3。
12. happens-before
happens-before原則定義如下:
(1)如果一個操作happens-before(之前發生)另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
(2)兩個操作之間存在happens-before關係,並不意味著一定要按照happens-before原則制定的順序來執行。如果重排序之後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。
happens-before規則如下:
(1)程式次序規則:一個執行緒內,按照程式碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
(2)鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作;
(3)volatile變數規則:對一個變數的寫操作先行發生於後面對這個變數的讀操作;
(4)傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
(5)執行緒啟動規則:Thread物件的start()方法先行發生於此執行緒的每個一個動作;
(6)執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷事件的發生;
(7)執行緒終結規則:執行緒中所有的操作都先行發生於執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到執行緒已經終止執行;
(8)物件終結規則:一個物件的初始化完成先行發生於他的finalize()方法的開始;
13. JMM
JMM(java memory model)java記憶體模型主要目標是定義程式中的變數,(此處所指的變數是例項欄位、靜態欄位等,不包含區域性變數和函式引數,因為這兩種是執行緒私有無法共享)在虛擬機器中儲存到記憶體與從記憶體讀取出來的規則細節,Java 記憶體模型規定所有變數都儲存在主記憶體中,每條執行緒還有自己的工作記憶體,工作記憶體儲存了該執行緒使用到的變數到主記憶體副本拷貝,執行緒對變數的所有操作(讀取、賦值)都必須在自己的工作記憶體中進行而不能直接讀寫主記憶體的變數,不同執行緒之間無法相互直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞均需要在主記憶體來完成。
14. ReentrantLock
未完待續。。。