【012期】JavaSE面試題(十二):多線程(2)

開篇介紹

你們好,我是Java最全面試題庫提褲姐,今天這篇是JavaSE系列的第十二篇,主要總結了Java中的多線程問題,多線程分爲三篇來說,這篇是第二篇,在後續,會沿着第一篇開篇的知識線路一直總結下去,作到日更!若是我能作到百日百更,但願你也能夠跟着百日百刷,一百天養成一個好習慣。java

啓動一個線程是調用 run() 方法仍是 start() 方法?

啓動一個線程是調用 start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由 JVM 調度並執行,這並不意味着線程就會當即運行。
run()方法是線程啓動後要進行回調(callback)的方法。面試

什麼狀況下致使線程死鎖,遇到線程死鎖該怎麼解決?

死鎖的定義:
死鎖是指多個線程因競爭資源而形成的一種互相等待狀態,若無外力做用,這些進程都將沒法向前推動。編程

死鎖的條件:
互斥條件:線程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某 資源僅爲一個線程
所佔有。此時如有其餘線程請求該資源,則請求線程只能等待。
不剝奪條件:線程所得到的資源在未使用完畢以前,不能被其餘線程強行奪走,即只能由得到該資源的線程本身來釋放(只能是主動釋放)。
請求和保持條件:線程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其餘線程佔有,此時請求進程被阻塞,但對本身已得到的資源保持不放。
循環等待條件:存在一種線程資源的循環等待鏈,鏈中每個線程已得到的資源同時被鏈中下一個線程所請求。即存在一個處於等待狀態的線程集合{Pl, P2, ..., pn},其中 Pi 等待的資源被 P(i+1)佔有(i=0, 1, ..., n-1),Pn 等待的資源被 P0 佔有,如圖所示:
循環等待.png多線程

避免死鎖:
①加鎖順序(線程按照必定的順序加鎖)
②加鎖時限(線程嘗試獲取鎖的時候加上必定的時限,超過期限則放棄對該鎖的請求,並釋放本身佔有的鎖)併發

什麼是樂觀鎖和悲觀鎖?

悲觀鎖
Java在JDK1.5以前都是靠synchronized關鍵字保證同步的,這種經過使用一致的鎖定協議來協調對共享狀態的訪問,能夠確保不管哪一個線程持有共享變量的鎖,都採用獨佔的方式來訪問這些變量。獨佔鎖其實就是一種悲觀鎖,因此能夠說synchronized是悲觀鎖性能

樂觀鎖
樂觀鎖( Optimistic Locking)實際上是一種思想。相對悲觀鎖而言,樂觀鎖假設認爲數據通常狀況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶決定如何去作。ui

樂觀鎖必定就是好的嗎?

樂觀鎖避免了悲觀鎖獨佔對象的現象,同時也提升了併發性能,但它也有缺點:
1.樂觀鎖只能保證一個共享變量的原子操做。若是多一個或幾個變量,樂觀鎖將變得力不從心,
但互斥鎖能輕易解決,無論對象數量多少及對象顆粒度大小。
2.長時間自旋可能致使開銷大。假如CAS長時間不成功而一直自旋,會給CPU帶來很大的開銷。
3.ABA問題。CAS的核心思想是經過比對內存值與預期值是否同樣而判斷內存值是否被改過,但這個判斷邏輯不嚴謹,假如內存值原來是A,後來被一條線程改成B,最後又被改爲了A,則CAS認爲此內存值並無發生改變,但其實是有被其餘線程改過的,這種狀況對依賴過程值的情景的運算結果影響很大。
解決的思路是引入版本號,每次變量更新都把版本號加一。this

說一下線程池的啓動策略?

線程池的執行過程描述:atom

/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/

線程池的執行過程.png

一、線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們。
二、當調用 execute() 方法添加一個任務時,線程池會作以下判斷:spa

  • ①若是正在運行的線程數量小於 corePoolSize,那麼立刻建立線程運行這個任務;
  • ②若是正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
  • ③若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要建立線程運行這

個任務;

  • ④若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告

訴調用者「我不能再接受任務了」。

三、當一個線程完成任務時,它會從隊列中取下一個任務來執行。
四、當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行的線程數大於corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。

線程池中的線程是怎麼建立的?是一開始就隨着線程池的啓動建立好的嗎?

不是。線程池默認初始化後不啓動 Worker,等待有請求時才啓動。

當調用 execute方法添加一個任務時,線程池會作以下判斷:

  • 若是正在運行的線程數量小於corePoolSize,那麼立刻建立線程運行這個任務;
  • 若是正在運行的線程數量大於或等於corePoolSize,那麼將這個任務放入隊列;
  • 若是這時候隊列滿了,並且正在運行的線程數量小於maximumPoolSize,那麼仍是要建立非核心線程馬上運行這個任務;
  • 若是隊列滿了,並且正在運行的線程數量大於或等於maximumPoolSize,那麼線程池會拋出異常RejectExecutionException

當一個線程完成任務時,它會從隊列中取下一個任務來執行。
當一個線程無事可作,超過必定的時間(keepAlive)時,線程池會判斷。
若是當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize的大小。

請說出同步線程及線程調度相關的方法?

wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理 InterruptedException 異常;
notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由 JVM 肯定喚醒哪一個線程,並且與優先級無關;
notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖
的線程才能進入就緒狀態;

注意:java 5 經過 Lock 接口提供了顯示的鎖機制,Lock 接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),加強了多線程編程的靈活性及對線程的協調
相關文章
相關標籤/搜索