线程池, 既然是个池子里面肯定就装很多线程.
如果并发的请求数量非常多, 但每个线程执行的时间很短, 这样就会频繁的创建和销毁 线程, 如此一来会大大降低系统的效率. 可能出现服务器在为每个请求创建新线程和销毁线 程上花费的时间和消耗的系统资源要比处理实际的用户请求的时间和资源更多. 因此 Java 中提供线程池对线程进行统一的管理使用.
线程池可以让多个任务重用线程. 减少线程创建, 消亡的开销, 提高性能. 当任务达到不需要等待线程创建便可立即执行. 提高线程的可管理性. 使用线程池进行统一的分配, 调优和监控.
Executor 框架体系:
Executor 接口是线程池框架中最顶级的接口, 定义了一个用于执行 Runnable 的 execute 方法
体系简要类图:
ExecutorService 也是一个重要的接口, 其中也定义了一系列重要的方法:
submit(task): 可用来提交 Callable 或 Runnable 任务, 并返回代表此任务的 Future 对象
shutdown(): 在完成已提交的任务后封闭办事, 不再接管新任务
shutdownNow(): 停止所有正在履行的任务并封闭办事.
isTerminated(): 测试是否所有任务都履行完毕了.
isShutdown(): 测试是否该 ExecutorService 已被关闭..
invokeXXX(task,...): 执行给定任务
从类图中可以看到一个很重要的实现类就是 ThreadPoolExecutor, 我们通过这个类看下线程池中比较重要的一些属性.
ctl 是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的信息: 线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount), 这里可以看到, 使用了 Integer 类型来保存, 高 3 位保存 runState, 低 29 位保存
workerCount.COUNT_BITS 就是 29,CAPACITY 就是 1 左移 29 位减 1(29 个 1), 这个常量表示 workerCount 的上限值, 大约是 5.3 亿.
线程池五种状态:
RUNNING:ctl 高三位 111, 可以接受新任务, 并且处理已经添加的任务. 线程池的初始化状态就是 running.
SHUTDOWN:ctl 高三位 000, 不接受新任务, 但是可以处理已经添加的任务. 调用 shutdown() 方法, 由 running 变成 shutdown
STOP:ctl 高三位 001, 不接受新任务, 不处理已添加的任务, 并且还会中断正在处理的任务. 调用 shutdownNow() 方法, 由 running/shutdown 变成 stop
TIDYING:ctl 高三位 010, 当所有的任务已终止, ctl 记录的任务数量为 0, 线程池会变为 tidying 状态. 当线程池变为 tidying 状态时, 会执行钩子函数 terminated().terminated() 在 ThreadPoolExecutor 类中是空的, 若用户想在线程池变为 tidying 时, 进行其他处理; 可以通过重写 terminated(). 线程池为 shutdown 状态, 并且阻塞队列是空的, 并且执行的任务也是空就会由 shutdown 变成 tidying;stop 状态时, 线程池中任务为空也会变成 tidying
TERMINATED:ctl 高三位 011, 线程池彻底凉凉了
当线程池处于 tidying 状态并且执行完了 terminated() 方法, 就会由 tidying 变成 terminated
构造方法:
corePoolSize: 线程池中的核心线程数, 当提交一个任务时, 线程池创建一个新线程执行任务, 直到当 前线程数等于 corePoolSize; 如果当前线程数为 corePoolSize, 继续提交的任务被保存到 阻塞队列中, 等待被执行; 如果执行了线程池的 prestartAllCoreThreads() 方法, 线程池会 提前创建并启动所有核心线程.
maximumPoolSize: 线程池中允许的最大线程数. 如果当前阻塞队列满了, 且继续提交任务, 则创建新的线程执行任务, 前提是当前线程数小于 maximumPoolSize;
keepAliveTime: 线程池维护线程所允许的空闲时间. 当线程池中的线程数量大于 corePoolSize 的时候, 如果这时没有新的任务提交, 核心线程外的线程不会立即销毁, 而是会等待, 直到等待的时间超过了 keepAliveTime.
unit:keepAliveTime 的单位.
workQueue: 用来保存等待被执行的任务的阻塞队列, 且任务必须实现 Runable 接口, 在 JDK 中提供了如下阻塞队列:
1,ArrayBlockingQueue: 基于数组结构的有界阻塞队列, 按 FIFO 排序任务;
2,LinkedBlockingQuene: 基于链表结构的阻塞队列, 按 FIFO 排序任务,
3,SynchronousQuene: 一个不存储元素的阻塞队列, 每个插入操作必须等到另一个线程调用移除操作, 否则插入操作一直处于阻塞状态.
4,priorityBlockingQuene: 具有优先级的无界阻塞队列;
threadFactory:ThreadFactory, 用来创建新线程. 默认使用 Executors.defaultThreadFactory() 来创建线程. 使用默认的 ThreadFactory 来创建线程时, 会使新创建的线程具有相同的 NORM_PRIORITY 优先级并且是非守护线程, 同时也设置了线程的名称.
Handler: 线程池的拒绝策略, 当阻塞队列满了, 且没有空闲的工作线程, 如果继续提交任务, 必须采取一种策略处理该任务, 线程池提供了 4 种策略:
1,AbortPolicy: 直接抛出异常, 默认策略;
2,CallerRunsPolicy: 用调用者所在的线程来执行任务;
3,DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务, 并执行当前任务;
4,DiscardPolicy: 直接丢弃任务;
上面的 4 种策略都是 ThreadPoolExecutor 的内部类. 当然也可以根据应用场景实现 RejectedExecutionHandler 接口, 自定义策略.
通过 execute 或者 submit 向线程池提交任务. 任务提交的时候先提交给核心线程 (corePoolSize); 如果核心线程满了, 就将任务放到 workQueue 里面去排队等待; 如果队列也满了 (取决用的什么队列, 以及设置的大小), 就会将新进来的任务提交给非核心线程, 非核心线程数量等于 maximumPoolSize - corePoolSize, 非核心线程使用之后会被回收. 如果非核心线程也满了, 那么就执行相应的拒绝策略 RejectedExecutionHandler.
源码解析:
1.execute 方法:
2. 可以看到关键方法是 addWork 方法, 可以看到在 addWork 方法先会进行一系列判断, 如果都通过了, 才会进行任务的创建
3.Worker 内部类, 这类是继承了 AbstractQueuedSynchronizer 并且实现了 Runable 接口, 其中还有两个重要属性一个是 Thread, 一个是 firstTask, 初始化的时候会吧 AQS 中的 state 字段设置 - 1, 后面允许中断会将这个值修改.
会通过线程工厂创建一个线程和当前的 worker 绑定, 创建线程的 runable 接口对象就是 work 本身. worker 重写的 run 方法实际调用了线程池的 runWorker 方法
4. 回到 addWorker 方法, 创建完了 worker, 可以就可以获取到绑定的线程了, 将 worker 添加到工作线程的集合中去. 然后调用对象绑定线程的 start 方法, 实际上会调用到 worker 的 run 方法, 进而调用线程池的 runWorker 方法
5. runWorker 方法中会去 worker 中取任务, 如果 firstTask 空, 就去队列中取, 因为之前在 addwork 的时候有些场景传入的 firstWork 是 null. 从队列取不到任务了, 也就是 getTask 返回 null 了, 结束 while 循环, 调用 processWorkerExit 方法移除任务, 处理最终的一些状态转换
就是说通过线程调用 worker 的 run 方法, 然后借助 worker 里面再来调用我们传入线程池中的任务的 run 方法. 所以说其实是起了一些线程, 然后调用 run 方法一直尝试去取任务, 取到之后手动调用的 run 方法执行任务.
来源: https://www.cnblogs.com/nijunyang/p/12941653.html