参考文章 由浅入深理解Java线程池及线程池的如何使用 java常用的几种线程池比较 java线程池与五种常用线程池策略使用与解析
上节中将线程比作货船,一艘货船要报废或者是新买都是一件非常耗费资源的事情,线程的使用也有该问题,当一个线程的任务执行完毕后,该线程会被自然销毁,当有新的任务要驱动时,又必须新建一个新的线程,这个过程对资源消耗是很大的。如下图所示
Executor将为你管理Thread对象,从而简化了并发编程,Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期。 具体实现是Java中引入了线程池的概念来解决复用线程的问题,一个线程驱动完任务后不会被销毁而是进入闲置状态,当有新任务来时闲置线程又可派上用场。
线程池的实现类->java.util.concurrent.ThreadPoolExecutor,可用构造方法来创建一个线程池对象
//ThreadPoolExecutor extends AbstractExecutorService //AbstractExecutorService implements ExecutorService //interface ExecutorService extends Executor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {}其中各个参数代表的含义如下
corePoolSize:线程池中核心线程的数量,即线程池保持的线程数量,当设置allowCoreThreadTimeOut为true后,核心线程在超时后可变为0maximumPoolSize:线程池允许的最大线程数,当线程数量超过corePoolSize时且阻塞队列(queueCapacity)已满时会新建线程驱动任务keepAliveTime:当线程数量超过核心线程数量时,多余的线程会等待一个时间看是否有新的任务进来,如无则终止多余的线程unit:上述超时时间的单位workQueue:阻塞队列,提交给线程池的任务队列threadFactory:创建一个新线程的工厂handler:表示当拒绝处理任务时的策略。除了用构造方法创建一个线程池外,Java还提供了一个工厂类java.util.concurrent.Executors来生成配置好的常用线程池方法,以下是它支持创建的四种类型的线程池。
CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,以达到线程都能被尽量复用的目的
FixedThreadPool会生成固定线程数量的线程池,这样可以一次性预先执行代价高昂的线程分配,也不用为每个任务都固定地付出创建线程的开销。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool,如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队执行,每个任务都会在下一个任务开始之前运行结束,所有的任务都将使用相同的线程
可让任务延迟或者是周期性地执行
该线程池生成方法是Java8才加入的,创建一个拥有多个任务队列(以便减少连接数)的线程池。 内部会创建ForkJoinPool,利用Work-Stealing算法,并行处理任务,不保证处理顺序。(摘自杨晓峰《Java核心技术36讲》)
通过调用方法
java.util.concurrent.ExecutorService#shutdown()可防止新任务被提交给这个Executor,当前线程将继续运行在shutdown()被调用之前提交的所有任务。
另一个关闭方法如下,该方法会尝试停止所有正在执行的任务,并返回正在等待执行的任务列表
List<Runnable> shutdownNow();Runnable是执行工作的独立任务并不返回任何值,如希望在任务完成时返回一个值,那么任务可通过实现Callable接口来实现,Callabel带有一个类型参数的泛型,表示从方法call()中返回值的类型,call()是实现Callable接口需要实现的方法. 实例如下
class TaskWithResult(var id:Int): Callable<String>{ override fun call(): String { return "result of TaskWithResult$id" } } fun main(args: Array<String>) { val exec = Executors.newCachedThreadPool() val results = ArrayList<Future<String>>() for (i in 0 until 10){ results.add(exec.submit(TaskWithResult(i))) } for(fs in results){ try { println(fs.get()) }catch (e: InterruptedException){ println(e) return }catch (e: ExecutionException){ println(e) return }finally { exec.shutdown() } } } /**output result of TaskWithResult0 result of TaskWithResult1 result of TaskWithResult2 result of TaskWithResult3 result of TaskWithResult4 result of TaskWithResult5 result of TaskWithResult6 result of TaskWithResult7 result of TaskWithResult8 result of TaskWithResult9 */submit()方法会产生Future对象,调用Future的get()方法可获取任务运行的结果,可用isDone()方法来决断任务是否完成,如果任务未完成时调用get()方法,则会阻塞线程,直至结果准备就绪。
由于线程的本质特性,当子线程发生异常而没有被捕获时,在主线程中用try-catch没法捕获子线程逃逸的异常,这样就会造成程序异常退出。 但通过Executor执行的任务,可以通过在生成不同线程池方法中可传入的ThreadFactory来捕获子线程的异常。实现过程是在生成每个Thread对象时附着一个异常处理器Thread.UncaughtException,Thread.UncaughtExecptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用。
实例如下
class ExceptionThread2: Runnable{ override fun run() { val t = Thread.currentThread() println("run() by $t") println("eh = ${t.uncaughtExceptionHandler}") throw RuntimeException() } } class MyUncaughtExceptionHandler: Thread.UncaughtExceptionHandler{ override fun uncaughtException(t: Thread?, e: Throwable?) { println("caught $e") //处理未捕获异常 } } class HandlerThreadFactory: ThreadFactory{ override fun newThread(r: Runnable?): Thread { println("${this}creating new Thread") val t = Thread(r) println("created$t") t.uncaughtExceptionHandler = MyUncaughtExceptionHandler() //这里设置新建线程的未捕获异常处理器 println("eh = ${t.uncaughtExceptionHandler}") return t } } fun main(args: Array<String>) { val exec = Executors.newCachedThreadPool(HandlerThreadFactory()) exec.execute(ExceptionThread2()) } /**output mutilthread.HandlerThreadFactory@266474c2creating new Thread createdThread[Thread-0,5,main] eh = mutilthread.MyUncaughtExceptionHandler@6f94fa3e run() by Thread[Thread-0,5,main] eh = mutilthread.MyUncaughtExceptionHandler@6f94fa3e mutilthread.HandlerThreadFactory@266474c2creating new Thread 这是线程池多创建的线程 createdThread[Thread-1,5,main] eh = mutilthread.MyUncaughtExceptionHandler@5cded4f1 同时也新建了一个handler的对象 caught java.lang.RuntimeException */实现流程就是自定义ThreadFactory类->自定义UncaughtExceptionHandler类->在ThreadFactory中生成的新线程附着一个UncaughtExceptionHandler对象->未捕获的异常会在自定义UncaughtExceptionHandler类中被处理. 如果想在所有的线程中都实现一样的未捕获异常处理器,则可通过设置Thread的静态域来达到,如下方法所示
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler)这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用
一个线程可以处于以下四种状态之一:
新建(new):当线程被创建时,它只会短暂地处于这种状态。此是它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态。就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。即是在任意时刻,线程可以运行也可以不运行。只要调度器能根本时间片给线程,它就可以运行,这不同于死亡和阻塞状态阻塞(Blocked):线程能够运行,但有某个条件阻止它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才有可能执行操作。死亡(Dead):处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。以上都是《Java编程思想》中关于线程状态的介绍,但在Java源码中Thread是有六种状态的,
public enum State { /**新建状态*/ NEW, /**就绪状态*/ RUNNABLE, /**阻塞状态*/ BLOCKED, /**等待状态*/ WAITING, /**超时等待状态*/ TIMED_WAITING, /**终止状态*/ TERMINATED; }这篇文章详细讲解了关于线程状态的切换, Java线程的6种状态及切换(透彻讲解)
