CPU核心数&线程池&设计模式&JUC

系列文章目录

文章目录


一、为什么要用线程池

降低资源消耗: 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低稳定性,通过重复利用已创建好的线程可以减低消耗

提高相应速度: 当任务到达时,可以不需要等待线程创建就能立即执行

提高线程的可管理性:线程池提供了一种限制,管理资源的策略,维护一些基本的线程统计信息

二、按照任务类型对线程池分类

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;
    }
相关推荐
失散132 小时前
分布式专题——23 Kafka日志索引详解
java·分布式·云原生·架构·kafka
云虎软件朱总2 小时前
配送跑腿系统:构建高并发、低延迟的同城配送系统架构解析
java·系统架构·uni-app
18538162800余+2 小时前
深入解析:什么是矩阵系统源码搭建定制化开发,支持OEM贴牌
java·服务器·html
李昊哲小课2 小时前
Spring Boot 基础教程
java·大数据·spring boot·后端
code123132 小时前
tomcat升级操作
java·tomcat
TanYYF3 小时前
HttpServletRequestWrapper详解
java·servlet
Swift社区3 小时前
Spring Boot 3.x + Security + OpenFeign:如何避免内部服务调用被重复拦截?
java·spring boot·后端
阿波罗尼亚4 小时前
复杂查询:直接查询/子查询/视图/CTE
java·前端·数据库
goTsHgo4 小时前
Spring XML 配置简介
xml·java·spring