线程池

线程池简介

如果没有使用线程池,会为每个请求都开一个新的线程,虽然理论上是可以的,但是会有缺点

  • 线程生命周期的开销非常高。每个线程都有自己的生命周期,创建和销毁线程所花费的时间和资源可能比处理客户端的任务花费的时间和资源更多,并且还会有某些空闲线程也会占用资源

  • 程序的稳定性和健壮性会下降,每个请求开一个线程。如果受到了恶意攻击或者请求过多(内存不足),程序很容易就奔溃掉了。

所以说:我们的线程最好是交由线程池来管理,这样可以减少对线程生命周期的管理,一定程度上提高性能。

线程池API

Excutor框架来使用线程池,它是线程池的基础

  • Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)

Callable和Future

线程池很多的API都有Callable和Future这两个类

可以简单认为:Callable就是Runnable的扩展

  • Runnable没有返回值,不能抛出受检查的异常,而Callable可以

也就是说:当我们的任务需要返回值的时,我们就可以使用Callable

Future一般我们认为是Callable的返回值,但他其实代表的是任务的生命周期(当然了,它是能获取得到Callable的返回值的)

public class CallableDemo implements Callable<Integer> {
    private int num;
    public CallableDemo(int num){
        this.num=num;
    }
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for(int i=1;i<=num;i++){
            sum=sum+i;
        }
        return sum;
    }
​
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建线程池对象
        ExecutorService service= Executors.newFixedThreadPool(2);
        // 可以执行Runnable对象或者Callable对象代表的线程
        Future<Integer> f1=service.submit(new CallableDemo(10));
        Future<Integer> f2=service.submit(new CallableDemo(20));
        // V get()
        int i1=f1.get();
        int i2=f2.get();
        // 结束
        service.shutdown();
        System.out.println(i1);
        System.out.println(i2);
    }
}

ThreadPoolExecutor详解

ThreadPoolExecutor最终变量ctl定义为AtomicInteger记录了“线程池中的任务数量”和“线程池的状态”两个信息

线程的状态:

  • RUNNING:线程池能够接受新任务,以及对新添加的任务进行处理。

  • SHUTDOWN:线程池不可以接受新任务,但是可以对已添加的任务进行处理。

  • STOP:线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务

  • TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。

  • TERMINATED:线程池彻底终止的状态

各个状态之间转换:

img

使用Executors创建线程池

一般使用Executors的工厂方法创建线程池,Executors三个比较常见的创建线程池方法:

  • newFixedThreadPool

    • 一个固定线程数的线程池,它将返回一个corePoolSize和maximumPoolSize相等的线程池

  • newCachedThreadPool

    • 非常有弹性的线程池,对于新的任务,如果此时线程池里没有空闲线程,线程池会毫不犹豫的创建一条新的线程去处理这个任务

  • SingleThreadExecutor

    • 单个worker线程的Executor

ThreadPoolExecutor构造方法

参数:

  • 指定核心线程数量

  • 指定最大线程数量

  • 允许线程空闲时间

  • 线程空闲时间的单位

  • 阻塞队列

  • 线程工厂

  • 任务拒绝策略

参数要点:

线程数量要点

  • 如果运行线程的数量少于核心线程数量,则创建新的线程处理请求

  • 如果运行线程的数量大于核心线程数量,小于最大线程数量,则当队列满的时候才创建新的线程

  • 如果核心线程数量等于最大线程数量,那么将创建固定大小的连接池

  • 如果设置了最大线程数量为无穷,那么允许线程池适合任意的并发数量

线程空闲时间要点:

  • 当前线程数大于核心线程数,如果空闲时间已经超过了,那该线程会销毁

排队策略要点

  • 同步移交:不会放到队列中,而是等待线程执行它。如果当前线程没有执行,很可能会新开一个线程执行。

  • 无界限策略:如果核心线程都在工作,该线程会放到队列中。所以线程数不会超过核心线程数

  • 有界限策略:可以避免资源耗尽,但是一定程度上减低了吞吐量

当线程关闭或者线程数量满了和队列饱和了,就有拒绝任务的情况了:

拒绝任务策略:

  • 直接抛出异常

  • 使用调用者的线程来处理

  • 直接丢掉这个任务

  • 丢掉最老的任务

ThreadPoolExecutor执行方法execute

execute执行方法分了三步:

  • 如果线程池中运行的线程数量<corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。

  • 如果线程池中运行的线程数量>=corePoolSize,且线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,就再次检查线程池的状态,

    • 如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。

    • 否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,新建线程对应的任务为null。

ThreadPoolExecutor线程池关闭

ThreadPoolExecutor提供了shutdown()shutdownNow()两个方法来关闭线程池

shutdown()

  • 按过去已提交任务的顺序执行一个有序的关闭,不接受新任务

  • 中断空闲线程

  • 将线程池状态设置为TERMINATED

shutdownNow()

  • 尝试停止所有的活动执行任务、暂停等待任务,并返回等待执行的任务列表

  • 正在运行的线程也会停止

区别

  • 调用shutdown()后,线程池状态立刻变为SHUTDOWN,而调用shutdownNow(),线程池状态立刻变为STOP

  • shutdown()等待任务执行完才中断线程,而shutdownNow()不等任务执行完就中断了线程。

原文地址:https://www.cnblogs.com/yjh1995/p/13514711.html