为什么使用线程池?
在java中,通过实现Runnable接口或者继承Thread类(Thread类的本质也是实现了Runnable接口)可以实现多线程,调用start()方法开启了多线程。
但是,如果频繁的手动去创建和销毁线程的话,会降低系统的运行效率。
通过使用线程池可以对线程实现复用,减少创建和销毁线程的次数,可以执行多个任务,大大降低系统资源消耗。
线程池的特点是,系统初始化会创建多个线程,放入线程池,需要使用的时候直接从线程池中取,不需要的时候就放回去,提高了工作效率。
线程池的工作原理:
1、线程池在初始化的同时,会自动创建一定数量的线程,存放起来;
2、当手动调用execute()和submit()方法的时候,会主动将任务提交到线程池;
3、线程池会判断核心线程数量是否已满,如果已满,则查看线程队列是否已满,否则创建线程执行任务;
4、将任务添加到线程队列,如果线程队列已满,则判断线程池是否已满,否则加入任务队列等待执行;
5、判断线程池最大线程数量是否已满,如果已满则拒绝执行任务,否则创建线程执行任务。
线程池销毁:
1、调用shutdown()方法,等到所有正在执行的任务全部执行完毕之后才会销毁;
2、调用shutdownNow()方法,则会试图停止所有正在执行的线程;
如何创建线程池?
java有预置线程池:newSingleThreadExecutor,newFixedThreadPool,newCacheedThreadPool,newScheduledThreadPool,newWorkStealingPool。如果不适合,还可以使用ThreadPoolExecutor创建自定义线程池。主要构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
可以看到,线程池创建的好坏与前四个参数有关。
1、corePoolSize: 核心线程数。刚创建线程池,没有,即不会预先创建。当任务到来,且当前线程没有超过corePoolSize,就会创建一个新线程执行该任务,即使其他线程是空闲。不会因为空闲而被释放,keepAliveTime不适用。
2、maximumPoolSize:最大线程数。如上面情况,如果当前线程超过corePoolSize,先尝试排队,如果队列满了或者其他情况不能入队,那么它不会排队,而是检查线程数是否达到maximumPoolSize,如果没有,就创建线程,直到线程数达到maximumPoolSize。
3、keepAliveTime:空闲线程存活时间。当线程池的线程数大于corePoolSize,额外空闲线程的存活时间。如果到了时间,还没有新任务,就会释放线程。值为0,表示线程不会超时释放。
4、unit:keepAliveTime的时间单位。
5、BlockingQueue:阻塞队列。可以使用LinkedBlockingQueue(默认无界)、ArrayBlockingQueue、PriorityBlockingQueue(无界)、SynchronousQueue(没实际存储空间)。使用无界队列,需要注意,线程数最多达到corePoolSize,新任务来只能排队,maximumPoolSize没意义。SynchronousQueue只有正好有空闲线程,才会入队成功,否则总是创建新线程,直到达到maximumPoolSize。
6、handler:任务拒绝策略。有界队列,线程数达到maximumPoolSize,队列满了,触发任务拒绝策略。四种处理方式:AbortPolicy(默认,抛出异常),DiscardPolicy(忽略新任务,不抛异常),DiscardOldestPolicy(扔掉等待时间最长,自己排队),CallerRunsPolicy(任务提交者线程执行任务)
最佳自定义创建线程池,队列有界,maximumPoolSize有限,使用任务拒绝策略。如果队列无界,服务不了的任务总是会排队,消耗内存,甚至引发内存不足异常。如果队列有界但maximumPoolSize无线,可能会创建过多线程,占内存和CPU。