Java线程池

发布时间:2024年01月09日


线程池:一个管理线程的池子

1. 为什么使用线程池?

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立刻执行
  • 提高线程的可管理性:统一管理线程,避免系统创建大量同类线程而导致消耗完内存
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);

2. 线程池执行原理?

创建新的线程需要获取全局锁,通过这种设计可以避免获取全局锁,当ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),提交的大部分任务都会被放到BlockingQueue
执行原理1
为了形象描述线程池的执行有,打个比喻:

  • 核心线程比作公司正式员工
  • 非核心线程比作外包员工
  • 阻塞队列比作需求池
  • 提交任务比作提需求

在这里插入图片描述

3. 线程池参数有哪些?

ThreadPoolExecutor的通用构造函数:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
  • corePoolSize:当有新任务时,如果线程池中线程数没用达到线程池的基本大小,则会创建新的线程执行任务,否则将任务放入阻塞队列。当线程池存活的线程数总是大于corePoolSize时,应该考虑调大corePoolSize

  • maxinumPoolSize:当阻塞队列填满时,如果线程池中线程数没用超过最大线程数,则会创建新的线程运行任务,否则更具拒绝策略处理新任务。非核心线程类似于临时借来的资源,这些线程在空闲时间超过keepAliveTime之后,就应该退出,避免资源浪费

  • BlockingQueue:存储等待运行的任务

  • keepAliveTime:非核心线程空闲后,报错存活的时间,此参数只对非核心线程有效。设置为0,表示多余的空闲线程会呗立刻终止

  • TimeUnit:时间单位

    TimeUnit.DAYS
    TimeUnit.HOURS
    TimeUnit.MINUTES
    TimeUnit.SECONDS
    TimeUnit.MILLISECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.NANOSECONDS
    
  • ThreadFactory:每当线程池创建一个新的线程时,都是通过线程工厂方法来完成的,在ThreadFactory中指定要了一个方法newThread,每当线程池需要创建新线程就会调用它

    public class MyThreadFactory implements ThreadFactory {
        private final String poolName;
    
        public MyThreadFactory(String poolName) {
            this.poolName = poolName;
        }
    
        public Thread newThread(Runnable runnable) {
            return new MyAppThread(runnable, poolName);//将线程池名字传递给构造函数,用于区分不同线程池的线程
        }
    }
    
  • RejectedExecutionHandler:当队列和线程池都满了时,根据拒绝策略处理新任务

    AbortPolicy:默认的策略,直接抛出RejectedExecutionException
    DiscardPolicy:不处理,直接丢弃
    DiscardOldestPolicy:将等待队列队首的任务丢弃,并执行当前任务
    CallerRunsPolicy:由调用线程处理该任务
    
  • ps:注意,这一段是根据源码分析的,具体的没贴出,这只是我的理解

    1. 若当前线程数小于核心线程数时,一定会创建新的线程去执行相应任务,不管线程是否有“空闲”,其实根本不会有空闲
    2. 承接上文,当线程池中创建新线程执行任务时(会构造一个worker对象,实际传进去的是runnable任务对象,外层用worker对象中构造的thread.start调用其中“run方法”,run方法不是runnablerun方法,而是worker对象的run方法,worker本身就实现了runnable接口),其主要代码块会在一个while函数内,while函数会判断传进来的runnable对象(worker对象传进去的)是否为空,第一次只要传进来的runnable不为空就直接调用runnablerun方法,第二次会去查找阻塞队列中是否含有任务(这是调用getTask方法),直到从阻塞队列中找到任务后返回runnable对象为止,说明该线程在创建后一直处于阻塞状态,直到阻塞队列中有任务之后才会进行,如果没有任务继续阻塞,这就解释了为什么线程池的不断重复利用线程的好处和效率问题
    3. 若当前线程数大于核心线程数时,也会执行相应方法,但前者执行的是take()方法,后者执行的是poll()方法,其实线程池的核心线程和非核心线程只是我们虚构出来的数据,我们在判断出若当前线程数大于核心线程数时和keepAliveTime的时长达到后,要除去相应线程是随机的,我们并没有给每个线程标号为核心线程
    4. poll方式取任务的特点是从缓存队列中取任务,最长等待keepAliveTime的时长,取不到返回null
    5. take方式取任务的特点是从缓存队列中取任务,若队列为空,则进入阻塞状态,直到能取出对象为止

总而言之:

  • 当有新任务来的时候,先看看当前的线程数有没有超过核心线程数,如果没有超过就直接新建一个线程来执行新的任务,如果超过了就看看缓冲(阻塞)队列有没有满,没满就将新任务放到缓存队列中,满了就新建一个线程来执行新的任务,如果线程池中的线程数已经达到了指定的最大线程数了,那就根据相应的策略拒绝任务
  • 当缓存(阻塞)队列中的任务都执行完了的时候,线程池中的线程数如果大于核心线程数,就销毁多出来的线程(这里是每次去执行getTask()方法时候销毁,里面有一部分代码块会进行判断),直到线程池中的线程数等于核心线程数,此时这些线程就不会被销毁了(除非你还设置了“核心线程数也可以被销毁”,allowCoreThreadTimeOut设置为true,默认是false),如果减去一个线程就是getTask方法返回了null,结束了前面提到的while循环,并且线程总记录数减一,否则它们就一直处于阻塞状态,等待新的任务到来,核心线程数就保持到我们设定的量了

4. 线程池大小怎么设置?

  • 如果线程池线程数量太小,当有大量请求需要处理,系统相应比较慢影响体验,甚至会出现任务队列大量堆积任务导致OOM
  • 如果线程池线程输了过大,大量线程可能会同时在争取CPU资源,这样会导致大量的上下文切换(cpu给线程分配时间片,当线程的cpu时间片用完后保存状态,以便下次继续运行),从而增加线程的执行实际,影响了整体执行效率
文章来源:https://blog.csdn.net/qq_53079156/article/details/120406398
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。