Java之线程池

1.了解线程池

在这里插入图片描述

创建线程池的类是ThreadPoolExecutor,继承关系如下:
请添加图片描述
其中 ThreadPoolExecutor提供了4个有参构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

2.ThreadPoolExecutor类的参数

  • corePoolSize(必需):核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
  • maximumPoolSize(必需):池中允许的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限。
  • keepAliveTime(必需):线程空闲超时时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
  • unit(必需)keepAliveTime参数的时间单位。有:TimeUnit.DAYS(天)、TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MICROSECONDS(微秒)、TimeUnit.NANOSECONDS(纳秒)
  • workQueue(必需):任务队列,采用阻塞队列实现。当核心线程全部繁忙时,后续由execute方法提交的Runnable将存放在任务队列中,等待被线程处理。
  • threadFactory(可选):线程工厂。指定线程池创建线程的方式。
  • handler(可选):拒绝策略。当线程池中线程数达到maximumPoolSizeworkQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。

拒绝策略:

拒绝策略需要实现RejectedExecutionHandler接口,不过Executors框架已经为我们实现了4种拒绝策略:

  • AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。(下面会举例)
  • CallerRunsPolicy:直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
  • DiscardPolicy:直接丢弃任务,不抛出任何异常。
  • DiscardOldestPolicy:将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。

线程初始化:

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

prestartCoreThread():boolean prestartCoreThread(),初始化一个核心线程
prestartAllCoreThreads():int prestartAllCoreThreads(),初始化所有核心线程,并返回初

始化的线程数
public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}

public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
        ++n;
    return n;
}

线程池容量调整:

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:

  • setCorePoolSize:设置核心池大小
  • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

3.ThreadPoolExecutor中get方法

  • getActiveCount():获取线程池正在执行任务的线程数量
  • getCorePoolSize():获取线程池核心线程的大小 = corePoolSize
  • getMaximumPoolSize():获取线程池最大线程数 = maximumPoolSize
  • getPoolSize(): 获取线程池中存活的线程数量(无论工作还是空闲)
  • getQueue().size():获取队列中排队的线程数量(进来的线程)
  • getCompletedTaskCount():获取执行完成的线程数(进来的线程)
  • getLargestPoolSize():线程池中最大同时执行的线程数

4.线程池的执行顺序

创建一个

  • 核心线程为:2,
  • 最大线程为:3,
  • 非核心线程空闲超过200毫秒回收,
  • 队列长度为:2

的线程池。

public static void main(String[] args) {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 200, TimeUnit.MILLISECONDS,
                    new ArrayBlockingQueue<Runnable>(2));
            for(int i=0;i<6;i++){
                MyTask myTask = new MyTask(i);
                executor.execute(myTask);
                System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
                        executor.getQueue().size()+",已执行完的任务数目:"+executor.getCompletedTaskCount());
            }
            executor.shutdown();
        }

    static class MyTask implements Runnable {
        private int taskNum;
        public MyTask(int num) {
            this.taskNum = num;
        }
        @Override
        public void run() {
            System.out.println("正在执行task "+taskNum);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task "+taskNum+"执行完毕");
        }
    }

打印:

线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:2,已执行完的任务数目:0
线程池中线程数目:3,队列中等待执行的任务数目:2,已执行完的任务数目:0
正在执行task 0
正在执行task 4
正在执行task 1
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.dataofx.employment.school.controller.ActivityController$MyTask@2280cdac rejected from java.util.concurrent.ThreadPoolExecutor@1517365b[Running, pool size = 3, active threads = 3, queued tasks = 2, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.dataofx.employment.school.controller.ActivityController.main(ActivityController.java:139)
task 0执行完毕
正在执行task 2
task 1执行完毕
正在执行task 3
task 4执行完毕
task 2执行完毕
task 3执行完毕

从控制台打印可以看出:线程池的执行顺序:

  1. 进来一个线程就开启一个核心线程
  2. 当核心线程占满后,进来的线程会放到等待队列中
  3. 当等待队列占满后,会放入到线程池的额外线程,直到达到最大线程数。
  4. 因为这里线程池能容纳的最大线程数 = 最大线程数(3) + 等待队列(2) = 5,但是这里做了6次循环,所以第六个线程进入线程池的时候被拒绝报错。

5.线程池的四种状态

  • RUNNING:当创建线程池后,初始时,线程池处于RUNNING状态;
  • SHUTDOWN:如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
  • STOP:如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务;
  • TERMINATED:当线程池处于SHUTDOWNSTOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

6. 如何设置线程池参数

核心线程数
线程核心数的设置理论公式:
CPU密集型任务,需尽量压缩线程数,参考值设置为N(CPU) + 1;
IO密集型任务,参考值可以设置为2*N(CPU);
示例:接口QPS为30,TP99为200ms,考虑系统抖动(QPS)
核心线程数:30 * 0.2 + 4(这个看各自系统的稳定性和网络波动)= 10
最大线程数 = 2 * 核心线程数

解释:
cpu 密集其实就是说这个请求进来之后一直在进行运算,计算量比较大,这时候为了避免cpu时间片切换线程执行,就根据cpu核心数 +1
密集型和上面 的区别就是他需要不停的访问三方或者数据库,一定会引起cpu时间片切换,此时设置为 cpu 核心数的两倍
QPS:一个接口1秒内被请求多少次,TP99:所有请求的99%都成功了此时的平均响应时间,系统抖动:各系统的稳定性以及网络波动

上面这个只是个理论值,实际可以在功能实现后进行压测测试,确定接口响应。上线后监控系统2-3天

7. submit 和 execute 区别

在Java中,线程池(ThreadPoolExecutor)提供了多种方法来提交任务并执行。其中,submit()方法和execute()方法是两种常用的提交任务的方式,它们的区别如下:

  • 返回值类型:submit()方法返回一个Future对象,而execute()方法没有返回值。
  • 异常处理:submit()方法可以通过Future对象来获取任务的执行结果、取消任务或处理任务抛出的异常;而execute()方法无法获取任务的返回结果或处理任务的异常,只能通过设置UncaughtExceptionHandler来处理任务内部抛出的异常。
  • 可接收任务类型:submit()方法可以接收Callable任务和Runnable任务,而execute()方法只能接收Runnable任务。

总结起来,主要区别在于submit()方法能够获取任务执行的结果,可以更方便地处理任务的返回值和异常;而execute()方法则更为简洁,适用于不需要获取返回值或处理异常的场景。

下面是一个示例代码,展示了submit()和execute()方法的使用:

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        // 使用submit()提交Callable任务
        Future<String> future = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Callable Task Result";
            }
        });
        
        try {
            String result = future.get();
            System.out.println("Callable Task Result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        // 使用execute()提交Runnable任务
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable Task Executed");
            }
        });

        executorService.shutdown();
    }
}

在上述示例中,我们首先使用submit()方法提交一个Callable任务,并通过Future对象的get()方法获取任务的返回结果。

接着,我们使用execute()方法提交一个Runnable任务,由线程池执行任务。

最后,我们调用线程池的shutdown()方法来关闭线程池。

总之,submit()方法在执行任务时提供了更多的控制和灵活性,适用于需要获取任务执行结果或处理异常的场景。而execute()方法则更为简洁,主要用于不需要关心任务结果或处理异常的情况。

8. 线程池使用规范

参考:Java 线程池规范

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值