系列文章目录
文章目录
- 系列文章目录
- 一、为什么要用线程池
- 二、按照任务类型对线程池分类
- 三、如果出现大量的timed_waiting
- [1 排查思路](#1 排查思路)
- 四、手写线程池测试
- 五、设计模式
- 六、AQS
一、为什么要用线程池
降低资源消耗: 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低稳定性,通过重复利用已创建好的线程可以减低消耗
提高相应速度: 当任务到达时,可以不需要等待线程创建就能立即执行
提高线程的可管理性:线程池提供了一种限制,管理资源的策略,维护一些基本的线程统计信息
二、按照任务类型对线程池分类
cpu密集型任务:执行计算任务,由于响应时间很快,cpu一直在运行,这种任务cpu的利用率很高
io密集型:执行io操作。由于执行io操作时间较长,导致cpu利用率不高,这类任务cpu常处于空闲状态
混合型:既要执行计算逻辑,又要进行io操作(如rpc调用,数据库访问),web服务器的http请求处理操作为此类任务的典型例子
1、为cpu密集型任务确定线程数
此类任务主要执行计算任务,由于响应时间很快,cpu一直在运行,这种任务cpu利用率很高。
比如4个核心的cpu, 通过4个线程并行的执行4个cpu密集型任务,此时的效率最高,如果线程数远远超过cpu核心数量,就需要频繁的切换线程,线程上下文切换需要消耗时间,反而会使得任务效率下降。所以对于cpu密集型任务来说,线程数等于cpu核心数。
2、为io密集型确定线程数
由于io密集型任务的cpu使用效率低,导致线程空闲时间很多,因此通常需要开cpu核心数两倍的线程。当io线程空闲时,可以启用其他线程继续使用cpu,提高cpu使用率。
netty的io处理任务就是典型的io密集型任务
c
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
3、混合型任务
cpu利用率也不是很高,非cpu耗时往往是cpu耗时数倍。比如在web应用中处理http请求,一次请求处理会包括db操作,rpc操作,缓存操作等耗时操作,一般来说,一次web请求的cpu计算耗时较少,而其他操作耗时更高,确定线程数,有一个公式:
最佳线程数=(线程等待时间+线程cpu时间/线程cpu时间)* cpu核数
三、如果出现大量的timed_waiting
顺便复习一下,线程的状态
NEW
RUNNABLE
WAITING
TIMED_WAITING
BLOCKED
TERMINATED
其中,timed_waiting表示线程正在等待一个时间限制内的资源或者条件,例如
c
Thread.sleep(long millis)
Object.wait(long timeout)
LockSupport.parkNanos()
Condition.awaitNanos()
Future.get(timeout)
如果代码中频繁调用sleep或者wait方法,会导致大量线程进入timed_waiting.从而影响性能。
连接池耗尽:如果连接池配置过小,或者有大量请求同时访问数据库,可能会导致线程在等到连接时进入timed_waiting
异步任务超时:如果使用了 CompletableFuture.get(timeout) 或 Future.get(timeout),当任务未完成时,调用线程会进入timed_waiting 状态。
线程池任务队列满:如果队列满,新任务会被拒绝或者等待,可能导致部分任务进入timed_waiting
网络io阻塞:如果线程在等待网络响应,且设置了超时时间,也会进入timed_waiting
1 排查思路
查看线程堆栈信息
检查资源瓶颈
数据库连接 连接池不足,导致线程等待
网络请求 网络延迟或超时,导致线程等待
线程池 任务队列满,线程等待任务
锁竞争 多线程争抢锁,进入等待
日志分析: 查看是否有异常抛出?查看是否有大量重复的请求或者死循环?查看是否有资源泄露
四、手写线程池测试
c
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class Test1 {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10,(queue,task)->{
/**
* (queue,task)->{}这是一个函数式对象,传给ThreadPool构造方法,然后被赋值给成员变量,然后执行到tryPut
* tryPut方法里面如果队列满,调用函数对象reject方法:rejectPolicy.reject(this,task);将队列本身以及task传过来
* 拿到队列和task执行:queue.put(task);
*
*
* */
//死等
queue.put(task);
});
for (int i = 0; i < 15; i++) {
int j=i;
threadPool.execute(()->{
try {
Thread.sleep(10000000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("{}",j);
});
}
}
}
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue, T task);
}
@Slf4j
class ThreadPool{
private RejectPolicy<Runnable> rejectPolicy;
private final long timeout;
private BlockingQueue<Runnable> taskQueue;
//线程集合
private HashSet<worker> workers=new HashSet();
//核心线程数
private int coreSize;
//获取任务的超时时间
private TimeUnit unit;
//执行任务
public void execute(Runnable task){
//当任务数没有超过coresize, 直接交给worker。
//超过,加入任务队列,暂存
synchronized (workers){
if(workers.size()<coreSize){
worker worker=new worker(task);
log.debug("新增worker:{}",worker);
workers.add(worker);
worker.start();
}else{
// taskQueue.put(task);
/**
* 策略模式:把操作抽象成一个接口,具体实现将来由调用者传递
* */
taskQueue.tryPut(rejectPolicy,task);
//死等
//带超时等待
//让调用者放弃任务执行
//让调用者抛出异常
//让调用者自己执行任务
}
}
}
//把线程包装成worker类
class worker extends Thread{
private Runnable task;
public worker(Runnable task) {
this.task = task;
}
@Override
public void run(){
//执行任务
//task不为空,执行任务
//task执行完,从task队列获取任务执行
//while(task!=null || (task=taskQueue.take())!=null){
while(task!=null || (task=taskQueue.poll(timeout,unit))!=null){
try{
log.debug("正在执行。。。{}",task);
task.run();
}catch (Exception e){
}finally {
task=null;
}
}
synchronized (workers){
log.debug("worker被移除:{}",this);
workers.remove(this);
}
}
}
public ThreadPool(int coreSize, long timeout,TimeUnit unit, int queueC,RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout=timeout;
this.unit = unit;
this.taskQueue=new BlockingQueue<>(queueC);
this.rejectPolicy=rejectPolicy;
}
}
@Slf4j
class BlockingQueue<T>{
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
//任务队列
private Deque<T> deque=new ArrayDeque<>();
//锁
private ReentrantLock lock=new ReentrantLock();
//生产者条件变量
private Condition fullWaitSet=lock.newCondition();
//消费者条件变量
private Condition emptyWaitSet=lock.newCondition();
private int capacity;
//带超时等待获取
public T poll(long timeout, TimeUnit unit){
lock.lock();
try{
long nacos = unit.toNanos(timeout);
while(deque.isEmpty()){
try {
//返回剩余时间,
if(nacos<=0){
return null;
}
nacos=emptyWaitSet.awaitNanos(nacos);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
T t = deque.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//阻塞获取
public T take(){
lock.lock();
try{
while(deque.isEmpty()){
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
T t = deque.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//阻塞添加
public void put(T task){
lock.lock();
try{
while(deque.size()==capacity){
//满了就不能放数据
try {
log.debug("等待加入任务队列:{}",task);
fullWaitSet.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("worker不够,加入任务队列:{}",task);
deque.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while(deque.size()==capacity){
//满了就不能放数据
try {
log.debug("等待加入任务队列:{}",task);
if(nanos<=0){
return false;
}
nanos=fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("worker不够,加入任务队列:{}",task);
deque.addLast(task);
emptyWaitSet.signal();
return true;
}finally {
lock.unlock();
}
}
public int size(){
lock.lock();
try{
return deque.size();
}finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try{
//队列是否以满
if(deque.size()==capacity){
//队列满,调用rejectPolicy
rejectPolicy.reject(this,task);
}else {
log.debug("加入任务队列{}",task);
deque.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
}
1、线程池状态
ThreadPoolExecutor 使用int 高3位表示线程池状态,低29位表示线程数量
状态名 | 高3位 | 说明 |
---|---|---|
RUNNING | 111 | |
SHUTDOWN | 000 | 不会接收新任务,但是会处理阻塞队列里面剩余任务 |
STOP | 001 | 会中断正在执行的任务,并抛弃阻塞队列任务 |
TIDYING | 010 | 任务全执行完毕,活动线程为0即将进入终结 |
TERMINATED | 011 | 终结状态 |
为什么这么设计?
这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次CAS原子操作进行赋值
//c为旧值,ctlOf返回结果新值
c
ctl.compareAndSet(c, **ctlOf**(targetState, workerCountOf(**c**)))
//rs(running status) 为高3代表线程池状态, wc(working count)为低29位代表线程个数,ctl是合并他们
c
private static int ctlOf(int rs, int wc) {return rs|wc;}
2、构造方法
task执行顺序:
核心(没有生存时间,执行完后仍保留在池中)--》阻塞队列---》救急(有生存时间,执行完毕则销毁掉。前提是用的有界队列,否则task一直向无界队列put)--》达到最大&队列已满---》拒绝策略
corePoolSize:核心线程数
mamximumPoolSoze: 最大线程数(核心+救急=最大)
keepAliveTime:生存时间,针对救急线程
unit 时间单位--针对救急线程
workQueue: 任务队列
threadFactory:线程工厂,给线程起一个名字
handler:
3、jdk提供的策略

4、newFixedThreadPool
核心线程数等于最大线程数,所以无需超时时间,阻塞队列无界,可以放置任意数量的任务,适用于任务量已知,相对耗时的任务
c
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
5、newCachedThreadPool
核心线程数是0,最大线程数是MAX_VALUE, 救急线程的空闲生存时间是60s, 意味着
全部都是救急线程
救急线程可以无限创建
队列采用了SynchronousQueue实现特点是,没有容量,没有线程来取是放不进去的
c
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
c
@Slf4j
public class TestSynchronousQueue {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> in = new SynchronousQueue<>();
new Thread(()->{
try{
log.debug("putting:{}",1);
in.put(1);
log.debug("{} putted",1);
log.debug("putting:{}",2);
in.put(2);
log.debug("{} putted",2);
}catch (InterruptedException e){
e.printStackTrace();
}
},"t1").start();
sleep(1000);
new Thread(()->{
try{
log.debug("taking:{}",1);
in.take();
}catch (InterruptedException e){
e.printStackTrace();
}
},"t2").start();
new Thread(()->{
try{
log.debug("taking:{}",2);
in.take();
}catch (InterruptedException e){
e.printStackTrace();
}
},"t3").start();
}
}
6、newSingleThreadExecutor
希望多个任务排队执行,线程数固定为1,任务数多于1时,放入队列,任务执行完毕,这唯一的线程也不会被释放
c
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
五、设计模式
异步模式之工作线程: 有限的工作线程异步处理无限多的任务,也可以将其归类为分工模式
六、AQS

抽象父类,是阻塞式锁(sync-阻塞式锁, CAS-无锁)
用state属性表示资源状态(分为独占模式,共享模式),子类需要定义如何维护这个状态,控制获取锁,释放锁
getState--获取state状态
setState-设置state状态
compareAndSetState--乐观锁机制设置state状态
独占模式是只有一个线程能够访问资源,共享模式允许多个线程访问资源
提供了基于FIFO的等待队列,类似于Monitor的EntryList
条件变量实现等待,唤醒机制,支持多个条件变量,类似于monitor的waitSet
1、加锁acquire和tryAcquire
tryAcquire: 尝试获取资源,仅尝试一次,不阻塞线程
非阻塞性: 不管是否获取成功,立即返回结果,不会让线程进入等待队列
返回值: true-获取资源成功, false-获取资源失败
无排队机制:失败时不会将线程加入AQS的等待队列,也不会触发自我中断等操作
acquire: 获取资源,阻塞式,带排队机制, 确保当前线程获取到资源,如果暂时无法获取,进入等待队列并阻塞,知道成功获取或者中断
阻塞性:如果tryAcquire失败,会将线程封装为节点加入AQS的同步队列,然后通过park阻塞线程
排队机制: 严格遵循FIFO队列规则,等待种的线程会按照顺序被唤醒(由前驱节点唤醒)
中断响应:支持响应线程中断,不会抛出interruptedException, 而是通过sefInterrupt记录中断状态
c
public final void acquire(int arg) {
if (!tryAcquire(arg))
acquire(null, arg, false, false, false, 0L);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 1. 尝试获取资源,成功则直接返回
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { // 2. 失败则入队并阻塞
selfInterrupt(); // 3. 若被中断,记录中断状态
}
}
2、解锁release和tryRelease
最大区别是tryRelease仅仅是把状态置为0, owner置为null, 但是不会唤醒等待队列中的线程, release会唤醒
c
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);
return true;
}
return false;
}