1.使用线程池的优点如下:
1.降低资源消耗:通过重复利用已创建的线程,降低线程创建与销毁带来的损耗。
2.提高响应速度:当新任务到达时,任务不需要等待线程创建就可以立即执行。
3.提高线程的可管理性:使用线程池可以统一进行线程分配、调度与监控。
1.线程池的继承关系
2.线程池的实现原理
当一个Runnable或Callable对象到达线程池时,执行策略如下:
第一步:
首先判断核心线程池的是否都在执行任务,如果是,再次查看核心线程是否已满,如果未满,创建新的线程执行任务如果核心线程池有空闲线程,则将任务直接分配给空闲线程执行。否则执行第二步…
第二步:
判断工作队列(BlockingQueue)是否已满,如果工作队列没有满,将提交任务存储到工作队列中等待核心池的调度;否则若工作队列已满,进入第三步…
第三步:
判断当前线程池的线程数是否已达到最大值maxiumSize,若已达到最大值,将任务交给饱和策略处理;否则,继续创建新线程执行此任务。
3.线程池的使用
1. 通过创建 ThreadPoolExecutor 来创建线程池:
public ThreadPoolExecutor(int corePoolSize, --- 1
int maximumPoolSize, --- 3
long keepAliveTime, --- 4
TimeUnit unit, --- 5
BlockingQueue<Runnable> workQueue,---2
RejectedExecutionHandler handler) ---6
1.corePoolSize(核心池的大小): 当提交任务到线程池时,线程池会创建一个新的线程来执行任务,即使核心池中有其他空闲线程也会创建新线程,一直到线程数达到核心池的大小为止。
调用prestartAllCoreThreads()线程池会提前创建并启动所有核心线程。
2.workQueue(工作队列): 用于保存等待执行任务的阻塞队列。可以选择以下几个阻塞队列。
-
ArrayBlockingQueue: 基于数组结构的有界阻塞队列,次队列按照FIFO原则对元素进行排序。
-
LinkedBlockingQueue: 基于链表结构的阻塞队列,按照FIFO排序元素,吞吐量高于ArrayBlockingQueue。
(Executors.newFixedThreadPool()采用此队列。) -
SynchronousQueue: 一个不存储元素阻塞队列。每个插入操作必须等到另一个线程的移除操作,否则,插入操作一直处于阻塞状态,吞吐量比LinkedBlockingQueue还要高。
Executors.newCachedThreadPool()采用此队列。 -
PriorityBlockingQueue: 具有优先级的无界阻塞队列。
3.maximumPoolSize(线程池最大线程数量): 线程池允许创建的最大线程数。如果队列已满并且创建的线程数小于此参数, 则线程池会创建新的线程执行任务。否则,调用饱和策略处理。如果采用无界队列,此参数无意义。
4.keepAliveTime(线程保持活动的时间): 线程池的工作线程空闲后,保持存活的时间。若任务很多,并且每个任务执行的时间较短,可以调大此参数来提高线程利用率。
5.TimeUnit (IV参数的时间单位)
6.RejectedExecutionHandler (饱和策略): 当队列和线程池都满了的情况,说明线程池处于饱和状态。此时,采用饱和策略来处理任务,默认采用AbortPolicy。
JDK一共内置4个饱和策略:
- AbortPolicy,表示无法处理新任务抛出异常,JDK默认采用此策略。
- CallerRunsPolicy,等待调用者线程空闲后执行此任务。
- DiscardOldestPolicy,丢弃阻塞队列中最近的一个任务,并执行当前任务。
- DiscardPolicy,不处理,直接将新任务丢弃,也不报错。
2. 手工创建线程池
以创建Runnable对象为例子:
运行结果: 截取部分足以说明情况。
循环5次只创建核心池大小。
以Callable为例:
运行结果:
FutureTask类执行任务只执行一次,并且会阻塞其他线程。
Future.get()会阻塞其他线程,一直等到当前Callable线程执行完毕后拿到返回值为止。