首先,前面几篇文章介绍了线程池池化、状态、参数、类型等相关知识,了解到了线程池池化技术的优势;线程池状态含义与转换方式;在不同的应用场景创建下根据不同的参数设置创建不同类型的线程池。这些都是线程池的概念、基础知识,接下来要说的就是线程池是如何执行任务的相关知识。
从上两图可以大致总结出线程池的工作原理
1.线程池刚创建时,里面没有一个线程,任务队列是作为参数传进来的,不过,就算队列里面有任务,线程池也不会马上执行它们。
2.当调用execute()方法添加一个任务时,线程池会做如下判断:
2.1.如果正则运行的线程数小于corePoolSize,那么马上创建线程运行这个任务。
2.2.如果正在运行的线程数量大于等于corePoolSize,那么将这个任务放入队列。
2.3.如果这时候队列满了,而且正在运行的线程数量小于maxmumPoolSize,那么还是要创建非核心线程立即运行这个任务。
2.4.如果队列满了,而且正在运行的线程数量大于或等于maxmumPoolSize,那么线程池会执行拒绝策略,抛出RejectExecutionException异常。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做,超过一定的时间(keepAliveTime)那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
其次,在我们使用线程池来执行一些任务时,线程池提供了两个常用的方法,即execute()和submit()。它们都可以用来提交任务,但在使用时需要注意它们之间的区别。
execute是Executor接口中声明的抽象方法,在ThreadPoolExecutor类中有具体实现。execute方法用于提交不需要返回值的任务,它将任务提交到线程池中执行,并立即返回,不会等待任务执行完成。如果任务执行过程中出现异常,线程池将捕获异常并将其记录下来。其他线程继续执行新任务。
submit是ExecutorService接口的方法,在AbstractExecutorService抽象类中已经有具体实现。submit方法用于提交需要返回值的任务,它会返回一个Future对象,可以用来获取任务执行结果。submit方法会将任务提交到线程池中执行,并立即返回,但不会阻塞当前线程,它会立即返回一个Future对象,通过该对象可以获取任务执行结果。如果任务执行过程中出现异常,submit无提示,其他线程继续执行。想要捕获异常信息需要通过Future对象的get()方法将会抛出异常。
需要注意的是,submit方法可以用于提交Callable任务和Runnable任务,而execute方法只能用于提交Runnable任务。因此,如果需要执行一个Callable任务并获取其结果,必须使用submit方法。
上面两个方法都提到了异常,那线程池中的线程出现异常了会发生什么
首先第一点,线程可能会抛出异常,因为线程池如果是excute()方法执行的任务,这个异常就正常抛出来了。
如果是submit()方法执行的FutureTask,那么异常会被捕获到FutureTask里面。而FutureTask在get的时候需要try..catch..那么异常自然是抛不出来的。而且在线程池内部调用run()方法时,会捕获到异常让run()方法正常执行,然后在catch里面把这个异常再抛出来。
其次,这个抛异常的线程,它不会影响到其他线程的正常运行。而且抛出异常的线程会通过一个叫做processWorkerExit()方法,然后将当前的工作线程从线程池的worker中移除。而且因为抛出异常,线程池的runWorker方法也就结束了,那么run方法也就结束了。当前线程也就结束了。
所以总结下来,当一个线程池中的线程抛出异常后,这个线程会根据任务的执行方法选择是否直接跑出异常。而且当前线程不会影响到其他线程,并且这个抛出异常的线程会被干掉。
如何获取和处理异常
1.使用try-catch。可以将submit和execute都能捕获到异常。
2.使用Thread.setDefaultUncaughtExceptionHandler方法捕获异常。只能处理execute方法。
3.重写afterExecute进行异常处理。可以处理两者抛出的异常。