Java中的线程池
- 一般我们说起Java中的线程池,其实指的是java.util.concurrent包下的ThreadPoolExecutor。当然java包下还有其他线程池的实现类,但主要也是最常用的就是这个类。今天我们来好好说说这个类。
- 这里我们结合了其他人的整理和自己的思考进行了总结。
1. 工作原理
如图所示:
当主线程中调用execute接口提交执行任务时:
则执行以下步骤:
注意:线程池初始时,是空的。
- 如果当前线程数<corePoolSize,如果是则创建新的线程执行该任务
- 如果当前线程数>=corePoolSize,则将任务存入BlockingQueue<Runnable>
- 如果阻塞队列已满,且当前线程数<maximumPoolSize,则新建线程执行该任务。
- 如果阻塞队列已满,且当前线程数>=maximumPoolSize,则抛出异常RejectedExecutionException,告诉调用者无法再接受任务了。
注意点:
- 线程池初始化时,是空的。
- 如果阻塞队列已满,且当前线程数<maximumPoolSize,则新建线程执行该任务。而不是新建线程,从阻塞队列里take任务来执行,所以这里并不是先来先执行的。
提问:这里的阻塞队列是BlockingQueue<Runnale>,我们知道ThreadPoolExecutor是支持Callable任务提交的,那这里不会有问题吗?
-
答:其实我们这里说的都是execute接口提交任务。execute接口只接受Runnable。其实我们看一下继承关系图:
-
可以看到其实ThreadPoolExecutor是继承了AbstractExecutorService,而AbstractExecutorService实现了ExecutorService。
-
且我们可以看到ThreadPoolExecutor类里只重写了execute方法。ExecutorService的其他方法都没有实现,而是在AbstractExecutorService里实现的。所以说整个ThreadPoolExecutor里的策略都是只针对execute方法来说的。所以说上述的工作原理只针对execute接口。像其他的submit/invoke接口并不适用--因为调用这些接口其实调用的是AbstractExecutorService的实现。
2. 我们来看一下ThreadPoolExecutor的参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:就是上图中的CorePoolSize,意为核心线程数。
- maximumPoolSize:就是上图中maximumPoolSize,意为整个线程池的最大线程数。这个数字是包含orePoolSize的。
- keepAliveTime和unit:是idle线程最大存活时间。unit是前者的单位。即如果当前线程数>corePoolSize,则会kill一些线程至corePoolSize大小。
- workQueue:就是上面说的阻塞队列,注意BlockingQueue<Runnable>只是个接口,下面我们会细说一下它的常用实现类。
- 下面两个参数是可选项:即Java重载了ThreadPoolExecutor的构造方法,下面两个参数可以不传,也可以只传一个。
- threadFactory:线程工厂。创建线程的接口,该接口只有一个方法:Thread newThread(Runnable r);我们可以实现这个接口进而自定义创建线程,比如制定线程名称,线程组等。
- handler:当线程池已满时,再提交任务会触发调用这个回调函数。RejectedExecutionHandler接口的唯一方法:
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
3. 阻塞队列的常用实现类
- ArrayBlockingQueue: 有边界的阻塞队列。长度大小初始化时制定,即内部由数组实现。
- LinkedBlockingQueue: 有边界的阻塞队列。边界是可选的,如果初始化的时候不指定则默认是Interger.MAX_VALUE,内部由链表实现。(最常用)
- PriorityBlockQueue: 带有优先级的阻塞队列。没有边界。
- 这三个类都实现了BlockingQueue接口,其中LinkedBlockingQueue最常用,特别注意一般一定要指定边界大小,不然线程池失去了些意义,且造成内存泄露。
这里就不对这些细说,以后我们再细说。
4. RejectedExecutionHandler线程池饱和策略
- 默认的 ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException。
- ThreadPoolExecutor.CallerRunsPolicy:调用线程直接call该任务的execute 本身。
- ThreadPoolExecutor.DiscardPolicy:将任务删除。
- ThreadPoolExecutor.DiscardOldestPolicy删除工作队列头部任务。
5. ThreadPoolExecutor的扩展
我们看ThreadPoolExecutor的源码发现如下三个函数的实现为空且是protected。明显用于子类实现的。
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
- 在执行任务的线程中将调用beforeExecute和afterExecute等方法,在这些方法中还可以添加日志、计时、监视或者统计信息收集的功能。
- 无论任务是从run中正常返回,还是抛出一个异常而返回,afterExecute都会被调用。如果任务在完成后带有一个Error,那么就不会调用afterExecute。
- 如果beforeExecute抛出一个RuntimeException,那么任务将不被执行,并且afterExecute也不会被调用。
- 在线程池完成关闭时调用terminated,也就是在所有任务都已经完成并且所有工作者线程也已经关闭后,terminated可以用来释放Executor在其生命周期里分配的各种资源,此外还可以执行发送通知、记录日志或者手机finalize统计等操作。
即我们可以子类来继承ThreadPoolExecutor来定制化一些功能。
链接:https://www.jianshu.com/p/f9c6bf9bb543
本文来自博客园,作者:茄子_2008,转载请注明原文链接:https://www.cnblogs.com/xd502djj/p/13307743.html