Java线程池实现原理之自我见解

9 篇文章 0 订阅
8 篇文章 0 订阅

Java线程池的执行流程图:

  1. 创建线程池,默认情况池子里面的线程数是为0,当有任务来的时候创建第一个线程。
  2. 当线程池中的工作线程数是否达到核心线程数后,如果未达到核心线程数,则创建一个核心工作线程执行任务。
  3. 如果已达核心线程数则将任务添加到阻塞对列中。
  4. 阻塞对列是否已满,没有满,直接add到阻塞对列,等待被执行。
  5. 如果阻塞队列已经满了,则比较当前工作线程数是否超过最大线程数,没有超过,创建一个非核心工作线程并执行任务,任务执行结束后会回收掉。
  6. 如果超过了最大线程数,将执行拒绝策略,默认策略是 AbortPolicy,可自定义拒绝策略。
    在这里插入图片描述

Java线程池有哪几种:

1. newSingleThreadPool

单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务,
LinkedBlockingQueue 阻塞队列
在这里插入图片描述

2. newFixedThreadPool

固定数量的线程池,用于已知并发压力的情况下,对线程数做限制,每提交一个任务就是一个线程,直到线程达到线程池的最大数量,超过最大数量的任务将进入阻塞队列,直到前面的任务完成后才继续执行;
核心线程数 = 最大线程数,是相等的看下面源码。
LinkedBlockingQueue 阻塞队列
在这里插入图片描述

3. newCachedThreadPool

可缓存线程池,一般60秒无执行的线程会被回收掉,当有任务时,会添加新线程来执行;
没有核心线程数,最大线程数是Interger最大值,超时时间60秒。一般都看作是无限制的线程池。
keepAliveTime = 60s
SynchronousQueue 阻塞队列
在这里插入图片描述

4. newScheduledThreadPool

特殊的线程池,支持定时和周期性的执行线程,实际业务场景中可以使用该线程池定期的同步数据。
自定义的核心线程数,最大线程数是Interger最大值,超时时间60秒。一般都看作是无限制的线程池。
在这里插入图片描述在这里插入图片描述

5. newSingleThreadScheduledExecutor

只有一个工作线程,可以按时间执行
在这里插入图片描述

6.newWorkStealingPool

JDK 1.8 后添加的,一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
在这里插入图片描述
前四种比较常用。

Java线程池的核心参数

在这里插入图片描述
在这里插入图片描述
corePoolSize: 核心线程数

1.核心线程数会一直存活,及时没有任何任务需要执行。
2.当工作线程数小于核心线程数时,即使有空闲线程,线程池也会优先创建新的线程处理。
3. 设置allowcoreThreadTimeout = true时,核心线程会超时关闭。

maximumPoolSize: 最大线程数

核心线程数满了 阻塞队列满了 这是线程数还不到最大线程数,可以new线程继续工作,这里new的线程就行临时工一样,run方法执行完后就是销毁线程

keepAliveTime: 空闲时间

1.当前线程达空闲时间达到keepAliveTime时,线程会退出,当前工作线程数量 => corePoolSize时, 则不会退出。
2.如果allowCoreThreadTimeout = true, 那所有的工作线程超时后都会退出。

nuit: 时间类型
workQueue: 阻塞队列
在这里插入图片描述
defalutHandler: 拒绝策略处理器

拒绝策略

AbortPolicy策略(默认值),丢弃任务,抛出运行时异常信息
在这里插入图片描述
在这里插入图片描述
CallerRunsPolicy策略,执行任务。
在这里插入图片描述
DiscardPolicy策略,忽视,什么都不做
在这里插入图片描述
DiscardOldestPolicy策略,从队列中踢出最靠前的一个元素,并执行它。
Queue 中 remove() 和 poll()都是用来从队列头部删除一个元素。
在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。
在这里插入图片描述
自定义拒绝策略, 实现RejectedExecutionHandler,重写rejectedExecution方法。

何时会触发拒绝策略

  1. 当前核心线程数已满,阻塞队列已满,最大线程数也满了,会拒绝新的任务。
  2. 当线程池被调用shotdown()方法后,会等线程池里的任务执行完毕后在shotdown操作。如果在调用shotdown()和线程真正shotdown之间提交任务吗,会拒绝新任务。

为什么不建议使用 Executors静态工厂构建线程池

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
具体说明:

  1. FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
  2. CachedThreadPool和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
    正确创建线程池
    避免使用Executors创建线程池,主要是避免使用其中的默认实现(Executor好多核心参数都隐藏了,不利于我们只管的使用,后期修改也是麻烦)。那么我们直接使用ThreadPoolExecutor类的构造器来创建线程池,BlockQueue也顺便指定合适的值了。
private static ExecutorService executor = new ThreadPoolExecutor(10,15,60L,TimeUnit,SECONDS, new ArrayBlockingQueue(20));

新创建线程池默认有几个线程,可变吗?

新创建的线程池默认情况是没有线程数的,但是有两个预热方法。

 ExecutorService executor = new ThreadPoolExecutor(10,15,60L,TimeUnit,SECONDS, new ArrayBlockingQueue(20));
 int coreNum = executor.prestartAllCoreThreads(); // 返回当前创建的核心线程数
 Boolean bo = executor.prestartCoreThread(); //当前核心线程是否创建成功

execute() 和 submit() 的区别

  1. 参数不同,execute参数是runnable类型,submit的参数可以是runnable和callable。
    void execute(Runnable command);
    submit(Runnable command)
    submit(Callable task)
    submit(Runnable task, T result);
  2. 返回结果不同,execute()没有返回值,submit()可以有返回值,适用于需要关注返回值的场景,

线程池关闭方法,以及区别

  1. 调用shotdown() 方法,线程池将不会在接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务,会等当前线程执行完成后shotdown操作。 在真正shotdown前如果有任务过来会执行拒绝策略。
  2. 调用shotdownNow()方法,对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。

初始化线程池时线程数的选择

  1. 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上,以此来尽量利用CPU资源。
  2. 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。

上述只是一个基本思想,如果真的需要精确的控制,还是需要上线以后观察线程池中线程数量跟队列的情况来定。

线程池都有哪几种工作队列

1、ArrayBlockingQueue

是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

2、LinkedBlockingQueue

一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列

3、SynchronousQueue

一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

4、PriorityBlockingQueue

一个具有优先级的无限阻塞队列。

什么是线程池?

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

为什么要使用线程池?

创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉)

线程池有什么作用?

线程池作用就是限制系统中执行线程的数量。

  1. 提高效率 创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。

  2. 方便管理 可以编写线程池管理代码对池中的线程同一进行管理,比如说启动时有该程序创建100个线程,每当有请求的时候,就分配一个线程去工作,如果刚好并发有101个请求,那多出的这一个请求可以排队等候,避免因无休止的创建线程导致系统崩溃。

过了空闲时间回收线程底层代码是怎样实现的吗?

t.isInterrupted() && w.tryLock() == true;

private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock; 
        mainLock.lock();	
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) { // 核心步骤!
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }
  • 7
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值