一篇就够,线程与线程池的那些事之线程池篇

本文深入探讨Java线程池的原理、好处与配置,包括线程池的好处、核心类、拒绝策略、创建方法、线程复用、参数配置以及动态监控,帮助开发者理解和优化线程池的使用。
摘要由CSDN通过智能技术生成

本文关键字:

线程 , 线程池 , 单线程 , 多线程 , 线程池的好处 , 线程回收 , 创建方式, 核心参数 , 底层机制 , 拒绝策略 , 参数设置 , 动态监控 , 线程隔离

线程和线程池相关的知识,是Java学习或者面试中一定会遇到的知识点,本篇我们会从线程和进程,并行与并发,单线程和多线程等,一直讲解到线程池,线程池的好处,创建方式,重要的核心参数,几个重要的方法,底层实现,拒绝策略,参数设置,动态调整,线程隔离等等。主要的大纲如下:

线程池的好处

线程池,使用了池化思想来管理线程,池化技术就是为了最大化效益,最小化用户风险,将资源统一放在一起管理的思想。这种思想在很多地方都有使用到,不仅仅是计算机,比如金融,企业管理,设备管理等。

为什么要线程池?如果在并发的场景,编码人员根据需求来创建线程池,可能会有以下的问题:

  • 我们很难确定系统有多少线程在运行,如果使用就创建,不使用就销毁,那么创建和销毁线程的消耗也是比较大的
  • 假设来了很多请求,可能是爬虫,疯狂创建线程,可能把系统资源耗尽。

实现线程池有什么好处呢?

  • 降低资源消耗:池化技术可以重复利用已经创建的线程,降低线程创建和销毁的损耗。
  • 提高响应速度:利用已经存在的线程进行处理,少去了创建线程的时间
  • 管理线程可控:线程是稀缺资源,不能无限创建,线程池可以做到统一分配和监控
  • 拓展其他功能:比如定时线程池,可以定时执行任务

其实池化技术,用在比较多地方,比如:

  • 数据库连接池:数据库连接是稀缺资源,先创建好,提高响应速度,重复利用已有的连接
  • 实例池:先创建好对象放到池子里面,循环利用,减少来回创建和销毁的消耗

线程池相关的类

下面是与线程池相关的类的继承关系:

Executor

Executor 是顶级接口,里面只有一个方法 execute(Runnable command) ,定义的是调度线程池来执行任务,它定义了线程池的基本规范,执行任务是它的天职。

ExecutorService

ExecutorService 继承了 Executor ,但是它仍然是一个接口,它多了一些方法:

  • void shutdown() :关闭线程池,会等待任务执行完。
  • List<Runnable> shutdownNow() :立刻关闭线程池,尝试停止所有正在积极执行的任务,停止等待任务的处理,并 返回一个正在等待执行的任务列表(还没有执行的) 。
  • boolean isShutdown() :判断线程池是不是已经关闭,但是可能线程还在执行。
  • boolean isTerminated() :在执行shutdown/shutdownNow之后,所有的任务已经完成,这个状态就是true。
  • boolean awaitTermination(long timeout, TimeUnit unit) :执行shutdown之后,阻塞等到terminated状态,除非超时或者被打断。
  • <T> Future<T> submit(Callable<T> task) : 提交一个有返回值的任务,并且返回该任务尚未有结果的Future,调用future.get()方法,可以返回任务完成的时候的结果。
  • <T> Future<T> submit(Runnable task, T result) :提交一个任务,传入返回结果,这个result没有什么作用,只是指定类型和一个返回的结果。
  • Future<?> submit(Runnable task) : 提交任务,返回Future
  • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) :批量执行tasks,获取Future的list,可以批量提交任务。
  • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) :批量提交任务,并指定超时时间
  • <T> T invokeAny(Collection<? extends Callable<T>> tasks) : 阻塞,获取第一个完成任务的结果值,
  • <T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) :阻塞,获取第一个完成结果的值,指定超时时间

可能有同学对前面的 <T> Future<T> submit(Runnable task, T result) 有疑问,这个reuslt有什么作用?

其实它没有什么作用,只是持有它,任务完成后,还是调用 future.get() 返回这个结果,用 result new 了一个 ftask ,其内部其实是使用了Runnable的包装类 RunnableAdapter ,没有对result做特殊的处理,调用 call() 方法的时候,直接返回这个结果。(Executors 中具体的实现)

public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            // 返回传入的结果
            return result;
        }
    }

还有一个方法值得一提: invokeAny() : 在 ThreadPoolExecutor 中使用 ExecutorService 中的方法 invokeAny() 取得第一个完成的任务的结果,当第一个任务执行完成后,会调用 interrupt() 方法将其他任务中断。

注意, ExecutorService 是接口,里面都是定义,并没有涉及实现,而前面的讲解都是基于它的名字(规定的规范)以及它的普遍实现来说的。

可以看到 ExecutorService 定义的是线程池的一些操作,包括关闭,判断是否关闭,是否停止,提交任务,批量提交任务等等。

AbstractExecutorService

AbstractExecutorService 是一个抽象类,实现了 ExecutorService 接口,这是大部分线程池的基本实现,定时的线程池先不关注,主要的方法如下:

不仅实现了 submit , invokeAll , invokeAny 等方法,而且提供了一个 newTaskFor 方法用于构建 RunnableFuture 对象,那些能够获取到任务返回结果的对象都是通过 newTaskFor 来获取的。不展开里面所有的源码的介绍,仅以submit()方法为例:

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        // 封装任务
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        // 执行任务
        execute(ftask);
        // 返回 RunnableFuture 对象
        return ftask;
    }

但是在 AbstractExecutorService 是没有对最最重要的方法进行实现的,也就是 execute() 方法。线程池具体是怎么执行的,这个不同的线程池可以有不同的实现,一般都是继承 AbstractExecutorService (定时任务有其他的接口),我们最最常用的就是 ThreadPoolExecutor 。

ThreadPoolExecutor

重点来了!!! ThreadPoolExecutor 一般就是我们平时常用到的线程池类,所谓创建线程池,如果不是定时线程池,就是使用它。

先看 ThreadPoolExecutor 的内部结构(属性):

public class ThreadPoolExecutor extends AbstractExecutorService {
    // 状态控制,主要用来控制线程池的状态,是核心的遍历,使用的是原子类
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  	// 用来表示线程数量的位数(使用的是位运算,一部分表示线程的数量,一部分表示线程池的状态)
    // SIZE = 32 表示32位,那么COUNT_BITS就是29位
    private static final int COUNT_BITS = Integer.SIZE - 3;
  	// 线程池的容量,也就是27位表示的最大值
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 状态量,存储在高位,32位中的前3位
  	// 111(第一位是符号位,1表示负数),线程池运行中
    private static final int RUNNING    = -1 << COUNT_BITS; 
  	// 000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
  	// 001
    private static final int STOP       =  1 << COUNT_BITS;
  	// 010
    private static final int TIDYING    =  2 << COUNT_BITS;
  	// 011
    private static final int TERMINATED =  3 << COUNT_BITS;

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值