目录
线程池的作用
线程池的使用
线程池的创建方式
线程池的解析
①Executors与ThreadPoolExecutor
②ThreadPoolExecutor线程池的构造方法
③RejectedExecutionHandler线程池的拒绝策略
固定线程数量线程池的简单模拟实现
线程池的作用
对于线程的使用,可能会频繁的创建与销毁.
但对于这些创建与销毁来说,会产生比较大的开销.因为对于线程的创建与销毁来说,与底层的操作系统息息相关.而像进入操作系统的任务,会被称为"内核态".在内核态当中会执行其他任务,同时内核态中的操作也是我们不可控的,频繁的创建销毁线程需要的成本会更大.
对此,就有了线程池.创造了线程放入"池"中管理,并调用执行一定的任务.在任务完毕后,我们对其并不会进行销毁,而是将其放回到"池"中去.极大的减少了开销.
除了减少了资源的开销,还能对线程在"池"中进行管理.
总结:
- 减少了资源开销,避免了线程的重复创建与销毁.
- 增添了对于线程的管理,将线程集中放到一起能够有效的进行管理.
- 提升了性能,任务对线程的调用响应的速度会增开,还是减少了线程的重复创建与销毁的操作.
- 限制了线程的个数,防止其无限制的创建.
线程池的使用
在java标准库中,提供了我们使用线程池.
线程池比较特殊,不用去实例化一个类.而是调用了Executors的一个类方法(静态方法)newFixedThreadPool去创建线程池.
返回值为ExecutorService.
ExecutorService pool = Executors.newFixedThreadPool(10);//在线程池中创建10个线程
pool.submit(new Runnable() {//调用submit方法,给线程们布置任务执行
@Override
public void run() {
System.out.println("wow");
}
});
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("haha");
}
});
线程池的创建方式
在Executors中线程池的创建大致分四种方法.
①newFixedThreadPool-创建固定线程数目的线程池
②newCachedThreadPool-创建线程数目动态增长的线程池
③newSingleThreadExecutor-创建单线程的线程池
④newScheduleThreadPool-创建可延迟执行命令的线程池(类似Timer)
线程池的解析
①Executors与ThreadPoolExecutor
上述对于线程池的创建的四种方法,其实本质上都是同一种方法:ThreadPoolExecutor.
Executors本质上只是对ThreadPoolExecutor类的封装.
源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
//在Exeucutors中实例化了一个ScheduledThreadPoolExecutor,这个类继承了ThreadPoolExecutor
可以看到都ThreadPoolExecutor这个类有关,Executors中不同种类的线程池只是在ThreadPoolExecutor传入的参数不同.
选择了要创建哪一种线程池,在Executor中会默认的帮你设定相应的参数.从而让我们更方便的使用.
②ThreadPoolExecutor线程池的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corPoolSize-在线程池中的线程数目,即使线程是空闲的.除非设置了allowCoreThreadTimeOut.
- maximumPoolSize-在线程池中允许的最大线程数目(如果线程池中的任务比较多,会暂时创建新的线程来协助完成任务.但这些新创建的线程最后是会被销毁的,所以有最大线程数=最初创建的线程数 + 临时线程数).
- keepAliveTime-临时新创建的线程的最大空闲等待新任务的时间,如果时间一到临时创建的线程将会被销毁.
- unit-keepAliveTime参数的时间单位.
- workQueue-一个阻塞队列,用来管理任务.
- threadFactory-工厂模式,辅助线程池创建线程
- handler-线程池的拒绝策略(下文会介绍)
③RejectedExecutionHandler线程池的拒绝策略
在ThreadPoolExecutor类中,实现了四个拒绝策略
拒绝的是什么?什么时候会触发拒绝条件?
前面我们提到,通过调用创建Executors的实例化对象的submit方法将任务添加到阻塞队列当中.
当任务数量达到一定的数量,队列中塞满了或者线程池中的每一个线程都在工作没有多余的线程时会触发拒绝条件.
AbortPolicy-抛出一个RejectedExecutionException异常
CallRunsPolicy-将被拒绝的任务返回给提交任务的线程执行
DiscardOldestPolicy-抛弃阻塞队列中队首的任务,并把新任务添加进来
DiscardPolicy-抛弃这个想要新加入的任务
固定线程数量线程池的简单模拟实现
分为:
- 有一个阻塞队列存放任务
- 创建相应数量的线程
- 提供一个submit方法添加任务
public class MyThreadPool extends Thread{
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//阻塞队列存放任务
public MyThreadPool(int n){//构造方法,在创建线程池的时候就创建相应数量的线程
for(int i = 0; i < n; i++){
Thread thread = new Thread(() -> {
while(true){
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
public void submit(Runnable runnable) throws InterruptedException {//将任务存放到阻塞队列的方法
queue.put(runnable);
}
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(3);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("wowo");
}
});
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("haha");
}
});
}
}