一、new Thread弊端
1.每次new Thread新建对象,性能查。
2.线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致司机或OOM(
Out Of Memory)。
3.缺少更多的功能,如更多执行、定期执行、线程中断。
二、线程池的好处
1.重用存在的线程,减少对象创建、消亡的开销,性能好。
2.可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
3.提供定时执行、定期执行、单线程、并发数控制等高级功能。
三、线程池相关的类
1.ThreadPoolExecutor
ThreadPoolExecutor中含有三个参数
1)corePoolSize:核心线程数量。
2)maximumPoolSize:最大线程数。
3)workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
常用的有三种队列,
SynchronousQueue
,
LinkedBlockingDeque
,
ArrayBlockingQueue
4)KeepAliveTime:非核心线程没有任务执行时最多保持多长时间终止。
5)unit:keepAliveTime的时间单位。
6)threadFactory:线程工厂,用来创建线程。
7)rejectHandler:拒绝处理任务时的策略。
8)线程池规则
下面都假设任务队列没有大小限制:
a.如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
b.
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
c.
如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
d.
如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
e.
如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
任务队列大小有限时
a.
当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
b.
SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
9)线程池的使用:
a.execute():提交任务,交给线程池执行。
b.submit():提交任务,能够返回结果(execute+Future)。
c.shutdown():关闭线程池,等待任务都执行完毕。
d.shutdownNow():关闭线程池,不等待任务执行完毕,会中断正在执行的线程。
e.getTaskCount():线程池已执行和未执行的任务总数。
f.getCompletedTaskCount():已完成的任务数量。
g.getPoolSize():线程池当前的线程数量。
h.getActiveCount():当前线程池中正在执行任务的线程数量。
2.Executor框架提供的四种线程池
executor框架提供了四种线程池,其内部都是返回一个ThreadPoolExecutor对象。
1)Executors.newCachedThreadPool:
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制)
2)
Executors.newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3)
Executors.newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
4)
Executors.newSingleThreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
3.线程池的合理配置
1)CPU密集型任务,就需要尽量压榨CPU,参考值可以尽量设置为CPU数量+1。
2)IO密集型任务,参考值可以设置为2*CPU数量。
注:使用线程池也需要慎重,有些场景下因为线程池中任务调度所造成的开销,可能会使我们得不偿失。