概述
使用线程池的好处:
降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
提高响应速度:任务到达时,无需等待线程创建即可立即执行。
提高线程的可管理性:线程是稀缺资源,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优、监控。
提供更多更强大的功能:线程池具备可扩展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池的种类,目前JDK内置了五种线程池:
1、Excutors.newFixedThreadPool();
创建一个定长线程池,可以控制线程最大并发数,超出的线程会在队列中等待。
newFixedThreadPool创建的线程池corePoolSize和MaxmumPoolSize是相等的,它使用的阻塞队列是LinkedBlockingQueue。
2、Excutors.newSingleThreadExecutor();
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务都按照指定顺序执行。
newSingleThreadExeccutor将corePoolSize和MaxmumPoolSize都设置为1,它使用的阻塞队列是LinkedBlockingQueue。
3、Excutors.newCachedThreadPool();
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程。
newCachedThreadPool将corePoolSize设置为0;MaxmumPoolSize设置为Integer.MAX_VALUE;它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,如果线程空闲时间超过60秒,就销毁线程。
4、Executors.newScheduledThreadPool();
创建一个定长线程池,支持定时及周期性任务执行。
它的线程数量是固定的,它可安排给定时延迟后运行命令或者定期地执行,这类线程池主要用于执行定时任务和具有固定周期的重复任务。
5、Executors.newWorkStealingPool(); // java8新出
newWorkStralingPool会创建一个含有足够多线程的线程池,来维持相应的并行级别,它会通过工作窃取的方式,使得多核的CPU不会闲置,总会有活着的线程让CPU去运行。
所谓工作窃取,指的是闲置的线程去处理本不属于它的任务。每个处理核,都有一个队列存储需要完成的任务。对于多核的机器来说,当一个核对应的任务处理完毕后,就可以去帮助其他的核处理任务。
newWorkStealingPool方法本质上就是一个ForkJoinPool。
参数的意义:
corePoolSize:线程池的核心线程数。 在创建了线程池后,线程池的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。 也可以预创建线程,主动调用prestartAllCoreThreads()或者prestartCoreThread()方法,在任务没有来之前就预创建corePoolSize个线程或者一个线程。
maxmumPoolSize:线程池允许创建的最大线程数。 如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没有什么效果。
keepAliveTime:线程活动保持时间。 线程池的工作线程空闲后,保持存活的时间。所以如果任何很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
unit:参数keppliveTime的时间单位,有7种取值。 可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS,千分之一毫秒)和毫微秒(NANOSECONDS,千分之一微秒)。
workQueue:任务队列。用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue.。 静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
threadFactory:创建线程的工厂。 可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
handler:饱和策略。 当队列和线程池都满了,说明线程池处于饱和状态,那么必须才去一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在的线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
线程池的工作流程(原理):
1、在创建了线程池后,等待提交过来的任务请求。
2、当调用execute()方法添加一个请求任务时,线程池会做如下判断
2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3 如果这时候队列满了且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务了;
2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断;
如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。
如何实现:线程池中的“线程”,其实是被抽象为了一个静态内部类worker,它基于AQS实现,存放在线程池的HashSet works成员变量中。
线程池的基本思想就是:从workQueue中不断取出需要执行的任务,放在works中进行处理。
工作中线程的选择:
线程池不允许使用Executors去创建,而是通过ThreadPoolExector的方式,这样的处理方式会让使用者更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SignleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
所以说应该用使用自定义的线程池。
————————————————————————————————
最后
以上就是健忘柠檬为你收集整理的java中线程池有哪几种、以及参数的意义、及工作原理的全部内容,希望文章能够帮你解决java中线程池有哪几种、以及参数的意义、及工作原理所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复