什么是线程池?
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。
为什么要使用线程池?
使用线程必须通过线程池提供,不允许在任何应用中显示的创建线程, 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统的资源开销,解决了资源不足问题,如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗内存或过度切换问题。
经常启动一个创建、启动和销毁一个线程都非常耗时间和资源,使用线程池管理可以提高程序执行效率。 在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用 线程池,必须对其实现原理了如指掌。
创建线程池的方式:
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待;开发中多数使用这种方式实现。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池的原理:
创建线程池使用了Executors工具类来创建,但是底层实际是使用了ThreadPoolExecutor的构造函数来实现,
使用:newFixedThreadPool.execute(new Job()); 这样的方式来提交一个任务到线程池中,所以核心的逻辑就是 execute() 函数了。
在具体分析之前先了解下线程池中所定义的状态,这些状态都和线程的执行密切相关:
RUNNING 自然是运行状态,指可以接受任务执行队列里的任务
SHUTDOWN 指调用了 shutdown() 方法,不再接受新任务了,但是队列里的任务得执行完毕。
STOP 指调用了 shutdownNow() 方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。
TIDYING 所有任务都执行完毕,在调用 shutdown()/shutdownNow() 中都会尝试更新为这个状态。
TERMINATED 终止状态,当执行 terminated() 后会更新为这个状态。
调用execute方法后所执行的逻辑:
1、获取当前线程池的状态。
2、当前线程数量小于 coreSize 时创建一个新的线程运行。
3、如果当前线程处于运行状态,并且写入阻塞队列成功。
4、双重检查,再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
5、如果当前线程池为空就新创建一个线程并执行。
6、如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略。
如何配置线程
线程池肯定是不是越大越好。 通常我们是需要根据这批任务执行的性质来确定的。
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务,比如 CPU 个数相当的大小。
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数。
参考文章:http://ifeve.com/how-to-calculate-threadpool-size/
优雅的关闭线程池
有运行任务自然也有关闭任务,其实无非就是两个方法 shutdown()/shutdownNow()
但他们有着重要的区别: shutdown() 执行后停止接受新任务,会把队列的任务执行完毕。
shutdownNow() 也是停止接受新任务,但会中断所有的任务,将线程池状态变为 stop。