2017-06-01 62 views
0

遇到下列代碼來到(修改,使我的觀點明確):線程監視器中的任何一點都會重新啓動一個死鎖線程?

class ThreadWithQueue extends Thread { 
     Queue<Msg> msgQueue; 
     public void run() { while (!isInterrupted()) {processMessage(queue.take());} 
}    

class MessageDispatcher { 
    ThreadsWithQueue[] threads; 
    ...      
    void dispatchMessage(Msg msg) { 
     int index = findMatchingThread(msg); 
     if (!threads[index].isAlive() || threads[index].isInterrupted()){ 
      try { 
       threadCreateLock.lock(); 
       threads[index] = createAndStartThread(); 
      } finally { 
       threadCreateLock.unlock(); 
      } 
     } 
     threads[index].queue.add(msg); 
    } 

也就是說,有一個「消息調度」,可以重新啓動(即創建一個新的)線程,如果它發現當前的是死了或中斷。

注意兩個類都寫了由同一作者,居住在同一個包。

我的問題
是否有這個任何一點從設計的角度/最佳實踐點「重啓死線」功能?
豈不是一個更好的辦法,只是有 '趕上(的Throwable){/ *忽略 /} *' 每個線程的運行()內方法(即內'ThreadWithQueue.run(){ ...}')?

+1

你從哪裏看到死亡線程正在重新啓動?我不知道你從哪裏得到這段代碼(這不太好 - 你應該給出一個鏈接並將屬於你自己的代碼放在StackOverflow中),但是我認爲如果線程死了,這會啓動一個新線程。無論如何,需要上下文來判斷這是好還是壞。 – RealSkeptic

+0

像catch(Throwable){/ * ignore * /}'這樣的事情通常不是一個好主意,如果throwables/exceptions沒有被處理,他們應該至少被記錄下來。 – Thomas

+0

在這種情況下會捕獲「Throwable」嗎?另外,catch(Throwable x){/ * ignored * /}'從字面上看並不是一個好方法。只要捕獲'Throwable'即使處理異常也不是一個好主意,因爲它會捕獲諸如「OutOfMemoryError」之類的東西。 – Radiodef

回答

1

一般情況下,它可能是重新啓動一個線程是有用的,而不是捕捉和報告異常,當你不知道是否處理代碼已經在特殊情況下正確地清理後繼續進行。最值得注意的是,只要線程處於活動狀態,就可能有ThreadLocal變量具有懸而未決的關聯值。

不過,這裏有一些奇怪的事情。使用isInterrupted()作爲重啓的標準並沒有多大意義,因爲前面的測試已經證明線程仍然活着。因此,它可能是這樣,即在processMessage內的一些地方的中斷將被使用和處置,被返回到主循環時重置。所以最後,你會有兩個正在運行的線程,其中一個在隊列爲空時永遠掛在queue.take()。這可能是因爲您可以排除當前代碼爲processMessage的這種情況,但是,您最好停止代碼的任何開發,以確保將來不會激活該定時炸彈。

此外,這裏有幾種可能的競賽條件。該線程可能會在isAlive()isInterrupted()被檢查後立即死亡,所以您仍然排隊等待死線程。但由於無論如何都不會嘗試將死線程的掛起消息轉移到新線程,所以當線程由於異常而死亡時,任意數量的消息可能會丟失。即使在dispatchMessage()之間發生了這種轉移,也意味着在線程死亡之後,直到在新線程開始處理懸空隊列之前,嘗試將新消息安排到同一線程爲止。顯然,如果隊列不爲空,產生新線程應該由異常處理程序觸發,避免這種延遲。

而兔子的洞更深。雖然threads[index]是在threadCreateLock下編寫的,但讀取時不會保留該鎖定,這是一箇中斷的同步。對數組元素更新的可見性沒有任何保證。一個線程可以讀取過時的引用,儘管另一個線程已經創建了一個新線程,但創建了一個新線程。更糟的是,一個線程可以讀取一個新的引用,該引用還沒有被持有該鎖的線程完全初始化。如果msgQueue確實不是final,就像您發佈的那樣,在這種情況下,線程可能會遇到null參考。

我強烈推薦使用fixed thread pool代替它,它完全具備發佈的代碼試圖實現的功能,但是由專家編寫了解多線程的這些缺陷。

+0

很好的回答,謝謝。仍然不確定固定線程池,因爲我需要保證具有相同ID的消息將被順序處理(即通過相同的'threads [i]') – Alexander

2

從設計/最佳實踐的角度來看,「重啓死線」功能 有什麼意義嗎?

請注意,如果舊代碼已死或中斷,您所提供的代碼會啓動一個新的線程。雖然這可能來自線程的run()引發的意外異常,但如果特定線程中斷,它也會自然死亡。後一種行爲是提供關閉系統的典型方式。

排隊代碼看起來很自然的我。當消息被呈現給它時,它確保選擇處理它的線程在事實上分派消息之前既不會死也不會死。

豈不是更好 做法只是有catch(Throwable) {/*ignore*/}每個 線程的run()法(內ThreadWithQueue.run() {...}即)裏面?

不是。首先,所有類型的Throwable的一攬子捕獲和忽略政策是一個完全不好的主意。更具體地說,你提出的建議是防止未捕獲的異常處理程序處理這些異常。其次,如上所述,未捕獲的異常是不是可能導致一個線程死,所以你提出會不會不必測試活躍度,當它開始新的線程解除消息調度什麼的唯一的事情找到需要。

+0

除了未捕獲的異常外,什麼會導致線程死亡? – Alexander

+0

@Alexander,一個線程從'run()'返回時死亡。在你的情況下,當'whileInterrupted()'方法在'while'謂詞中調用時返回'true'時會發生這種情況。這與例外沒有直接關係。 –

+0

謝謝@約翰。對我來說是有道理的,因爲我在這裏看到職責分離:'經理'/'調度員'處理任何可能的問題,而'工作人員'只是對其工作/業務職能負責,並且可能對終止更加無知條件 – Alexander