本文共 5188 字,大约阅读时间需要 17 分钟。
为什么使用线程池:(在之前已经是使用过三种创建多线程的方式 那么为什么还要有线程池的方式 一个新技术的出现一定是有它的独到之处 )
线程池的优势:
线程池的主要特点:
线程池的继承关系:Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
使用JDK自带的线程池:使用的类 Executors工具类 但是底层上实际是new ThreadPoolExecutor()
创建一个:有固定线程数的线程池:newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
创建一个线程池中只能有一个线程的线程池:newSingleThreadExecutor 创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
创建一个可扩容的线程池:newCachedThreadPool创建的线程池将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程
虽然是在JDk中的线程池 但是ali开发手册中却是不推荐使用:
上面的线程池底层都是使用的是ThreadPoolExecutor 这里就详细解释一下:
构造函数:构造函数又非常多种 这一种是最基本的 也就是7个参数的
public ThreadPoolExecutor(int corePoolSize, // 线程池中常驻的核心线程数 int maximumPoolSize, // 线程池中能容纳同时执行的最大线程数 此值必须大于等于1 long keepAliveTime, // 多余的空闲线程的存活时间 TimeUnit unit, // 时间单位 BlockingQueueworkQueue, // 任务队列 被提交但是还没有执行的任务 ThreadFactory threadFactory, // 线程工厂,一般不修改 使用默认的线程工厂来创建线程 RejectedExecutionHandler handler // 拒绝策略) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler;}
参数的说明:
AbortPolicy
丢弃任务并抛出RejectedExecutionException异常 这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态 如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现 CallerRunsPolicy
由调用线程处理该任务 从哪来回哪去 这里就是main线程去处理 DiscardOldestPolicy
丢弃队列最前面的任务,然后重新提交被拒绝的任务。DiscardPolicy
和第一种一样 也是丢弃任务 但是不抛出异常 如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃自定义线程池的使用:
package com.interview.concurrent.threadpool;import java.util.concurrent.*;public class ThreadPoolExecutorDemo { public static void main(String[] args) { threadPoolExecutor(); } public static void threadPoolExecutor(){ ExecutorService threadPool = new ThreadPoolExecutor( 2, // 核心池子的大小 5, // 线程池最大大小5 2L, // 空闲线程的保留时间 TimeUnit.SECONDS, // 超时回收空闲的线程 new LinkedBlockingDeque<>(3), // 根据业务设置队列大小,队列大小一定要设置 原因上面说过了 Executors.defaultThreadFactory(), // 不用变 一般使用默认的 new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略 看这样的创建方式就知道拒绝策略是 ThreadPoolExecutor的静态内部类 ); try { // 队列 RejectedExecutionException 拒绝策略 for (int i = 1; i <= 10; i++) { // 默认在处理 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+" running...."); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }}
线程池处理流程:非常重要 (常见的面试题)
在创建了线程池后,线程池中的线程数为零
当调用execute()
方法添加一个请求任务时,线程池会做出如下判断:
当一个线程完成任务时,它会从队列中取下一个任务来执行
当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉 线程池的所有任务完成后,它最终会收缩到corePoolSize的大小
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // ctl 是一个原子整型 //addWorker 才是创建线程的方法 也就是任务提交的时候才创建线程 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } // 不是直接创建非核心线程 而是先放在workQueue中 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
线程池中最大线程数如何配置:
CPU 密集型:最大支持多少个线程同时跑,根据CPU去设置,一般设置成与CPU处理器一样大,每一次都要去写吗? 通过Runtime来获取
Runtime.getRuntime.availableProcessors();// 获取当前的可用核数
IO 密集型:磁盘读写、 一个线程在IO操作的时候、另外一个线程在CPU中跑,造成CPU空闲。最大线程数应该设置为 IO任务数! 对于大文件的读写非常耗时,我们应该用单独的线程让他慢慢跑
转载地址:http://lfxrn.baihongyu.com/