submit()和execute()的区别
submit()和execute()的区别
submit()和execute()都是用来向线程池提交任务的方法。
execute() 是Executor接口中定义的方法,只能提交Runnable任务,没有返回值不能获取任务执行结果,如果任务抛出异常,异常会由线程池的未捕获异常处理器处理。
源码体现
▼java复制代码public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. * * @param command the runnable task * @throws RejectedExecutionException if this task cannot be * accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command); }
submit() 是ExeCutorSevice 接口中定义的方法,可以提交Runnable任务以及Callable任务,返回**Future**对象,可以用来获取任务执行结果或取消任务,对于**Runnable**任务,返回的**Future****在任务完成后会返回null,**异常会被捕获并存储在Future中,只有在调用Future.get()时才会抛出
源码体现
▼java复制代码// ExecutorService 继承Executor public interface ExecutorService extends Executor { /** * Submits a value-returning task for execution and returns a * Future representing the pending results of the task. The * Future's {@code get} method will return the task's result upon * successful completion. * * <p> * If you would like to immediately block waiting * for a task, you can use constructions of the form * {@code result = exec.submit(aCallable).get();} * * <p>Note: The {@link Executors} class includes a set of methods * that can convert some other common closure-like objects, * for example, {@link java.security.PrivilegedAction} to * {@link Callable} form so they can be submitted. * * @param task the task to submit * @param <T> the type of the task's result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ <T> Future<T> submit(Callable<T> task); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return the given result upon successful completion. * * @param task the task to submit * @param result the result to return * @param <T> the type of the result * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ <T> Future<T> submit(Runnable task, T result); /** * Submits a Runnable task for execution and returns a Future * representing that task. The Future's {@code get} method will * return {@code null} upon <em>successful</em> completion. * * @param task the task to submit * @return a Future representing pending completion of the task * @throws RejectedExecutionException if the task cannot be * scheduled for execution * @throws NullPointerException if the task is null */ Future<?> submit(Runnable task); }
解释一下,上述三种的重载submit方法();
第一种 <T> Future<T> submit(Callable<T> task);
可以提交一个Callable任务,可以返回返回该线程的执行结果。
▼java复制代码Future<String> future = threadPool.submit(() -> { Thread.sleep(1000); return "Hello from Callable!"; }); System.out.println(future.get()); // 输出 "Hello from Callable!"
第二种: Future submit(Runnable task, T result);
可以提交一个Runnable 任务,同时可以指定执行后的返回结果。我们知道Runnable是没有返回值的,有时候我们需要一个表示来判断程序是否执行完毕,就可以执行返回值
▼java复制代码Future<String> future = threadPool.submit( () -> { try { System.out.println("Processing data..."); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }, "SUCCESS" // 任务完成后返回这个字符串 ); System.out.println(future.get()); // 输出 "SUCCESS"(而不是 null)
假设你有一个任务,它不返回计算结果(Runnable),但你仍然希望 Future 返回一个状态信息(如 "SUCCESS" 或 "FAILED"),而不是 null。这时就可以用这个方法。
第三种:Future<?> submit(Runnable task);
提交一个Runnable 任务,没有返回值。
▼java复制代码Future<?> future = threadPool.submit(() -> { System.out.println("Running a Runnable task"); }); future.get(); // 返回 null
在分析一下异常处理的不同
在使用execute 提交任务并执行线程的时候,发生异常,如果没有被捕获,直接打印在控制台中
▼java复制代码public class ExecuteUncaughtException { public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); threadPool.execute(() -> { System.out.println("任务开始执行..."); throw new RuntimeException("这是一个未捕获的异常!"); // 未捕获的异常 }); // 后续任务可能不会执行(因为线程可能已终止) threadPool.execute(() -> System.out.println("后续任务")); threadPool.shutdown(); } }
输出
▼plain复制代码任务开始执行... Exception in thread "pool-1-thread-1" java.lang.RuntimeException: 这是一个未捕获的异常! at ExecuteUncaughtException.lambda$main$0(ExecuteUncaughtException.java:10) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
execute处理异常的缺点。
- 异常如果没有被捕获,直接抛出,打印到控制台。
- 线程池中的线程可能因异常终止,导致后续任务无法执行(特别是
newSingleThreadExecutor只有一个线程时)。
再来分析一下submit,处理异常的方式
在使用submit 提交任务并执行线程的时候,发生异常,会存在FatureTask中,只要当ft.get()的时候,这个异常才会抛出.
▼java复制代码import java.util.concurrent.*; public class SubmitUncaughtException { public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); Future<?> future = threadPool.submit(() -> { System.out.println("任务开始执行..."); throw new RuntimeException("这是一个未捕获的异常!"); }); try { //使用future.get,就在编译时必须处理异常 future.get(); // 在这里抛出 ExecutionException } catch (ExecutionException e) { System.err.println("捕获到 Future 中的异常: " + e.getCause()); // 获取原始异常 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 后续任务正常执行 threadPool.execute(() -> System.out.println("后续任务")); threadPool.shutdown(); } }
输出
▼plain复制代码任务开始执行... 捕获到 Future 中的异常: java.lang.RuntimeException: 这是一个未捕获的异常! 后续任务
上述,先要异常不影响,线程的执行,还是使用trycatch,处理异常了,才不影响线程的执行的。
那我使用execute,也捕获异常,不就和sumbit一样的了吗
▼java复制代码ExecutorService threadPool = Executors.newSingleThreadExecutor(); threadPool.execute(() -> { try { System.out.println("任务开始执行..."); throw new RuntimeException("这是一个捕获的异常!"); } catch (RuntimeException e) { System.err.println("捕获到异常: " + e.getMessage()); // 捕获并处理 } }); // 后续任务正常执行 threadPool.execute(() -> System.out.println("后续任务")); threadPool.shutdown();
输出
▼java复制代码任务开始执行... 捕获到异常: 这是一个捕获的异常! 后续任务
结果显示,当使用execute时,捕获并处理异常和sumbit的执行过程时一样的。
那么为什么,那么多人说使用sumbit处理异常更加强大呢?
说比execute 处理异常强大的理由都是:execute 在执行中,如果异常未被捕获,就会直接打印在控制台,同时终止该线程的声明周期,submit 在执行过程中,出现异常会由Future 捕获,线程不会终止,这种说法,没啥逻辑可言,使用submit执行线程,线程没打断的原因是,trycatch 处理了异常,同样的在execute 使用try catch 处理异常,也可以做到相同的效果。
我觉得submit比execute 好用的原因,有两点:第一点**使用submit执行线程时,通过future.get()获取执行结果,需要显示的try catch,(为啥说显示的,原因就是future.get() 是受检时异常,也就是不try catch 在编译简短就报错),**其实这点也不算太强大,有好的编程习惯,以及熟练使用execute的人,使用execute 在线程中try catch.也可以达到相同的效果。第二点:才是真正的强大之处,execute****异常信息无法向上传递,**execute()** 的异常被消化后,调用方完全不知道任务失败了(除非手动记录日志)。**submit()** 的异常可通过 **Future.get()** 传递给调用方,适合需要统一错误处理的场景。
做一下总结,在实际开发的过程种,经常使用ExcutorSevice接口,因为其继承了Excutor接口,也就是可以ExcutorService 既可以使用execute方法和submit方法。不过submit更加强大,支持返回值(甚至Runnable 也有预期的返回值),更好的异常处理(execute 如果 Runnable 任务中抛出未捕获的异常异常会直接传播到线程池的未捕获异常处理器(默认打印堆栈,但程序不会停止**)线程会因异常退出**,可能导致线程池中的线程减少,影响后续任务执行。
更多请看:Java开发面试指南 以及Github:https://github.com/hnsqls/interview 如果有用,感谢关注
