В конце прошлого года я играл с запланированными задачами для мониторинга кластера Neo4j, и одна из проблем, с которыми я столкнулся, заключалась в том, что мониторинг иногда прекращался.
В конце концов я понял, что это потому, что RuntimeException создавался внутри метода Runnable, и я не обрабатывал его. Следующий код демонстрирует проблему:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class RunnableBlog { public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate( new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " -> " + System.currentTimeMillis()); throw new RuntimeException( "game over" ); } }, 0 , 1000 , TimeUnit.MILLISECONDS).get(); System.out.println( "exit" ); executor.shutdown(); } } |
Если мы запустим этот код, то увидим RuntimeException, но исполнитель не выйдет, потому что поток умер, не сообщив об этом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
Exception in thread "main" pool- 1 -thread- 1 -> 1391212558074 java.util.concurrent.ExecutionException: java.lang.RuntimeException: game over at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java: 252 ) at java.util.concurrent.FutureTask.get(FutureTask.java: 111 ) at RunnableBlog.main(RunnableBlog.java: 11 ) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 57 ) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43 ) at java.lang.reflect.Method.invoke(Method.java: 601 ) at com.intellij.rt.execution.application.AppMain.main(AppMain.java: 120 ) Caused by: java.lang.RuntimeException: game over at RunnableBlog$ 1 .run(RunnableBlog.java: 16 ) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java: 471 ) at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java: 351 ) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$ 301 (ScheduledThreadPoolExecutor.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java: 293 ) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1110 ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 603 ) at java.lang.Thread.run(Thread.java: 722 ) |
В то время я добавил блок try catch и распечатал исключение следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public class RunnableBlog { public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate( new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " -> " + System.currentTimeMillis()); throw new RuntimeException( "game over" ); } catch (RuntimeException e) { e.printStackTrace(); } } }, 0 , 1000 , TimeUnit.MILLISECONDS).get(); System.out.println( "exit" ); executor.shutdown(); } } |
Это позволяет распознать исключение, и, насколько я могу судить, означает, что поток, выполняющий Runnable, не умирает.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
java.lang.RuntimeException: game over pool- 1 -thread- 1 -> 1391212651955 at RunnableBlog$ 1 .run(RunnableBlog.java: 16 ) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java: 471 ) at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java: 351 ) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$ 301 (ScheduledThreadPoolExecutor.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java: 293 ) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1110 ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 603 ) at java.lang.Thread.run(Thread.java: 722 ) pool- 1 -thread- 1 -> 1391212652956 java.lang.RuntimeException: game over at RunnableBlog$ 1 .run(RunnableBlog.java: 16 ) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java: 471 ) at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java: 351 ) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$ 301 (ScheduledThreadPoolExecutor.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java: 293 ) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1110 ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 603 ) at java.lang.Thread.run(Thread.java: 722 ) pool- 1 -thread- 1 -> 1391212653955 java.lang.RuntimeException: game over at RunnableBlog$ 1 .run(RunnableBlog.java: 16 ) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java: 471 ) at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java: 351 ) at java.util.concurrent.FutureTask.runAndReset(FutureTask.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$ 301 (ScheduledThreadPoolExecutor.java: 178 ) at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java: 293 ) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java: 1110 ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java: 603 ) at java.lang.Thread.run(Thread.java: 722 ) |
Это сработало хорошо и позволило мне продолжать мониторинг кластера.
Однако недавно я начал читать « Параллелизм Java на практике » (всего через 6 лет после того, как купил его!) И понял, что это может быть неправильным способом обработки исключения RuntimeException.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public class RunnableBlog { public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate( new Runnable() { @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " -> " + System.currentTimeMillis()); throw new RuntimeException( "game over" ); } catch (RuntimeException e) { Thread t = Thread.currentThread(); t.getUncaughtExceptionHandler().uncaughtException(t, e); } } }, 0 , 1000 , TimeUnit.MILLISECONDS).get(); System.out.println( "exit" ); executor.shutdown(); } } |
Я не вижу большой разницы между этими двумя подходами, поэтому было бы здорово, если бы кто-то мог объяснить мне, почему этот подход лучше, чем мой предыдущий способ перехвата исключения и печати трассировки стека.