线程的创建
两种方式,一种是new一个ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数说明省略
另一种方式,使用Executors类中提供的几个静态工厂方法来创建线程池:
//来任务就创建线程,当线程空闲超过60秒,销毁线程。 里面调用 ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
Executors.newCachedThreadPool();
//创建容量为1的缓冲池,里面调用 ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
Executors.newSingleThreadExecutor();
//创建固定容量大小的缓冲池,里面调用 ThreadPoolExecutor(int, int, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
Executors.newFixedThreadPool(int);
但是!!!阿里巴巴Java开发手册中指出
提交任务的两种方式
- Callable
该类任务有返回结果,可以抛出异常。
通过submit函数提交,返回Future对象。
可通过get获取执行结果。 - Runnable
该类任务只执行,无法获取返回结果,并在执行过程中无法抛异常。
通过execute提交。
区别:submit方法内部本质上还是调用的execute方法,submit方法能用Future获取调用的返回结果。
线程的中断
shutdown():将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
shutdownNow():遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
线程的销毁
受制于keepAliveTime。当线程不再被使用时,不会自动关闭。因此为了确保线程关闭,通过设置合理的keep-alive times,设置核心线程(core threads)数目为0或设置allowCoreThreadTimeOut(boolean)。只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0
任务队列
//无界队列,由链表实现,当线程数超过corePoolSize时,采用该队列则不会再创建线程。此时maximumPoolSize的时不会起作用。
//如果任务处理速度小于任务到达速度,采用这种队列的话,队列的长度会越来越长
LinkedBlockingQueue
//有界队列,由数组实现,需要考虑队列大小和最大线程池数量之间的折中。使用较长的队列和较少的最大线程池数量,会减少CPU使用、系统资源和上下文切换开销,但是吞吐量低。
//相反,使用较短的队列通常需要较大的最大线程池数量,会使CPU更忙碌但是调度开销大,也会使吞吐量降低。
ArrayBlockingQueue
//同步阻塞队列,该队列不储存元素。
//每个插入操作必须等到另一个线程调用移除操作
SynchronousQueue
//有界队列,优先级阻塞队列
PriorityBlockingQueue
队列吞吐量比较:
SynchronousQueue > LinkedBlockingQueue > ArrayBlockingQueue
饱和策略
当实际线程数超过最大线程数,并且阻塞队列已满时,就会调用饱和策略。
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
- ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程数量设置
-
CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。
因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。 -
IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。
IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。 -
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目 http://ifeve.com/how-to-calculate-threadpool-size/
参考:
https://www.cnblogs.com/my376908915/p/6761364.html
https://zhuanlan.zhihu.com/p/33264000
https://zhuanlan.zhihu.com/p/32867181