目录
一、为什么使用线程池
二、标准库中的线程池
三、线程池的简单代码实现
四、标准库中线程池的构造方法解读
五、拒绝策略
一、为什么使用线程池
如果有多个线程,一个一个的去创建则需要从就绪状态被调度到运行状态不断切换然后死亡,这样的情况成本比较高。所以频繁的开启或停止线程,线程需要重新被CPU调度到运行状态,需要发生CPU的上下文切换,效率比较低。而线程池是复用机制,它会提前创建好一些固定的进入运行状态的线程,从而减少就绪状态到运行状态的切换
二、标准库中的线程池
public static void main(String[] args) throws InterruptedException {// 1. 创建线程池ExecutorService pool = Executors.newCachedThreadPool();// 2. 提交任务pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("线程1"); }});}
三、线程池的简单代码实现
一个线程池可以同时提交n个任务,在线程池中会有m个线程去执行这n个任务,我们可以基于生产者消费者模型来实现线程池,首先我们需要一个阻塞队列来存储提交的任务,然后在构造方法中创建出m个线程去执行这些任务
class Pool {// 1. 创建阻塞队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);// 2. 提交任务public void submit(Runnable task) throws InterruptedException {queue.put(task);}// 3. 构造方法中创建出线程public Pool (int m) {for (int i = 0; i < m; i++) {Thread t = new Thread() {@Overridepublic void run() {while (true) {try {queue.take().run();} catch (InterruptedException e) {e.printStackTrace();}}}};t.start();}}
}
四、标准库中线程池的构造方法解读
我们先来看一下线程池的核心构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);
}
其中第一次个参数corePoolSize表示线程池里核心线程数的个数,第二个参数表示线程池中最大线程数,既然有了核心线程数那么为什么会有最大线程数呢?举个例子,一个公司在任务不多的情况下可能只需要核心的员工就够了,但是忽然有一天任务忽然增多原有的核心员工不够了这个时候就会招聘对应数量的实习生来执行这些任务,最大线程数就是核心线程数加上临时的线程数,那接下来的两个参数也是针对这些“实习生”的,他表示当任务不紧急的多久时间后销毁这些线程,第五个参数也就是workQueue,在上面线程池的实现中我们了解了在线程池中有一个专门存储任务的任务队列,这个参数也是一个任务队列,在创建线程池的时候你可以选择传入一个自己的任务队列,也可以选择不传入,就好比大一新生开学你可以选择学校提供的被褥也可以选择自带被褥,关于最后一个参数handler,他是用于选择线程池使用哪一个拒绝策略的,会在下面提到
五、拒绝策略
什么是线程池的拒绝策略呢?当线程池中的任务队列已经满了时,再有新的任务提交进来时线程池会根据不同的拒绝策略采取不同的措施》》》
- ThreadPoolExcutor.AbortPolicy:直接抛出异常,之前的任务也会停止
- ThreadPoolExcutor.CallerRunsPolicy:将任务交给调用者去执行,如果调用者执行不了,该任务会被丢弃
- ThreadPoolExcutor.DiscardoldestPolicy:将最老的任务丢弃来执行新的任务,执行完成后再去执行老的任务
- ThreadPoolExcutor.DiscarPolicy:先继续执行之前的任务,后续再去执行新提交的任务