2017-06-22 22 views
4

假設我有一個像下面的代碼:爲什麼並行性ForkJoinPool會使我的異常加倍?

Future<Object> executeBy(ExecutorService executor) { 
    return executor.submit(() -> { 
     throw new IllegalStateException(); 
    }); 
} 

有使用ForkJoinPool#commonPool時是沒有問題的,但是當我使用的平行ForkJoinPool這將增加一倍的IllegalStateException。例如:

executeBy(new ForkJoinPool(1)).get(); 
//        ^--- double the IllegalStateException 

Q1:爲什麼並行ForkJoinPoolException發生在Callable

Q2:如何避免這種奇怪的行爲?

+3

[相關](HTTPS://計算器

該功能可以很容易地通過投擲不具有匹配的公共構造在F/J可以使用,像本整齊內部類的異常類型禁用。 com/q/38994907/2711488) – Holger

回答

10

如果異常已在工作線程中拋出並將原始異常設置爲其原因,則叉/連接池通常會嘗試在調用者的線程內重新創建異常。這就是你認爲的「加倍」。當您仔細觀察堆棧軌跡時,您會注意到這兩個例外之間的區別。

公共泳池在這方面沒有什麼不同。但公共池允許調用者線程在等待最終結果時參與工作。所以,當你改變你的代碼

static Future<Object> executeBy(ExecutorService executor) { 
    return executor.submit(() -> { 
     throw new IllegalStateException(Thread.currentThread().toString()); 
    }); 
} 

,你會發現,它經常發生的調用者線程處於呼叫get()更快,做的工作竊取比工作線程方法中可以去接任務。換言之,您的供應商已在主/主線程內執行,在這種情況下,將不會重新創建該例外。

static Future<Object> executeBy(ExecutorService executor) { 
    return executor.submit(() -> { 
     throw new IllegalStateException() { 
       @Override 
       public String toString() { 
        String s = getClass().getSuperclass().getName(); 
        String message = getLocalizedMessage(); 
        return message!=null? s+": "+message: s; 
       } 
      }; 
    }); 
} 
+0

嗨,@霍爾。有沒有一種簡單的方法來避免這種奇怪的行爲?我可以認爲唯一的方法是檢查一個並行性'ForkJoinPool',然後包裝返回的'Future'。 –

+2

我增加了一種可能性。 – Holger

+1

我的意思是,除了看到由IllegalStateException導致IllegalStateException引發的堆棧外,真正的奇怪行爲是什麼?在我看來,這是爲了妥善保存行號和堆棧的純度。 – AnthonyJClink

8

ForkJoinPool創建ForkJoinTask實例來執行您的提交。

ForkJoinTask嘗試在發生異常時提供精確的堆棧跟蹤。它javadoc狀態

重新拋出異常的行爲以同樣的方式作爲常規的例外,但是, 如果可能的話,包含堆棧跟蹤(如顯示例如使用 ex.printStackTrace())都發起 計算線程以及該線程實際遇到異常;最低限度只有後者。

這是the comment in the private implementation of this behavior

/** 
* Returns a rethrowable exception for the given task, if 
* available. To provide accurate stack traces, if the exception 
* was not thrown by the current thread, we try to create a new 
* exception of the same type as the one thrown, but with the 
* recorded exception as its cause. If there is no such 
* constructor, we instead try to use a no-arg constructor, 
* followed by initCause, to the same effect. If none of these 
* apply, or any fail due to other exceptions, we return the 
* recorded exception, which is still correct, although it may 
* contain a misleading stack trace. 
* 
* @return the exception, or null if none 
*/ 
private Throwable getThrowableException() { 

換句話說,它採用IllegalStateException你的代碼拋出,發現收到ThrowableIllegalStateException一個構造函數,調用該構造與原IllegalStateException爲它的參數,並返回結果(然後在ExecutionException內重新生成)。

您的堆棧跟蹤現在還包含get調用的堆棧跟蹤。

隨着ForkJoinPool爲您ExecutorService,我不相信你可以避開它,它是依賴於如果異常沒有被當前線程和構造函數中拋出的異常類型的可用拋出。

+0

謝謝先生。但我想避免這種奇怪的行爲。 –

+3

@ holi-java使用不同的ExecutorService實現,或者從沒有無參數構造函數的類或者帶有Throwable參數的構造函數中引發異常。另外,你可以隨時解開。 –

相關問題