线程池ThreadPoolExecutor使用

为什么不使用Executors工厂方法,创建线程池

使用Executors创建ThreadPoolExecutor会出现OOM,导致资源耗尽
故使用ThreadPoolExecutor的构造方法直接创建在这里插入图片描述
构造方法对应参数的说明见 https://cloud.tencent.com/developer/article/1674364

ThreadPoolExecutor构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

构造方法参数

加点的为核心参数

    • corePoolSize:线程池中核心线程数的最大值; 默认为1。
      设置规则:
      CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1
      IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
    • maximumPoolSize:指定线程池中的最大线程数量;默认为Integer.MAX_VALUE
  1. keepAliveTime:当线程池中线程数量超过 corePoolSize 时,空闲线程的存活时间; 默认为60s

  2. unit:keepAliveTime 的单位;

    • workQueue:任务队列,存放提交尚未被执行的任务;
    1. LinkedBlockingQueue 推荐常用
      默认队列容量为Integer.MAX_VALUE(可以理解为无限大), 内部使用的是链表进行储存,数据的新增删除效率比ArrayBlockingQueue更快,可以通过参数指定队列容量,正式环境下需要对其进行指定,避免导致OOM
    2. ArrayBlockingQueue
      没有默认容量,必须参数指定队列的容量,内部是使用数组结构进行存储,新增删除较LinkedBlockingQueue慢些,因为数组插入删除涉及到元素的移动,会很耗时
    3. 同步移交队列SynchronousQueue
      如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
  3. threadFactory:线程工厂,用于创建线程指定线程名称等,一般用默认的即可;
    为了统一在创建线程时设置一些参数,如是否守护线程,线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。
    它是一个接口类,而且方法只有一个,就是创建一个线程。
    如果没有另外说明,则在同一个ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。
    通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。
    如果从newThread 返回 null 时ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。

  4. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。
    拒绝策略,默认是AbortPolicy,会抛出异常。
    当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
    当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
    AbortPolicy 丢弃任务,抛运行时异常。
    CallerRunsPolicy 由当前调用的任务线程执行任务。
    DiscardPolicy 忽视,什么都不会发生。
    DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

线程执行顺序

  1. 开始没有激活的线程,需要创建线程,直到数量等于corePoolSize
  2. corePoolSize占用满了,还有新任务,则进入 blockingQueue等待,
  3. blockingQueue占用满了,还有新任务,则创建新线程,直到数量等于maximumPoolSize,
  4. maximumPoolSize也占用满了,还有新任务,则执行拒绝策略

所有同一时间线程池能承载的任务数量是 maximumPoolSize + blockingQueue容量

在这里插入图片描述

ThreadPoolExecutor和spring封装的ThreadPoolTaskExecutor案例

ThreadPoolExecutor是Java的线程池
ThreadPoolTaskExecutor是spring封装的线程池

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

@Slf4j
@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数目
        executor.setCorePoolSize(i * 2);
        //指定最大线程数
        executor.setMaxPoolSize(i * 2);
        //队列中最大的数目
        executor.setQueueCapacity(i * 2 * 10);
        //线程名称前缀
        executor.setThreadNamePrefix("ThreadPoolTaskExecutor-");
        //rejection-policy:当pool已经达到max size的时候,如何处理新任务
        //CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        //对拒绝task的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //当调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //线程空闲后的最大存活时间
        executor.setKeepAliveSeconds(60);
        //加载
        executor.initialize();
        log.info("初始化线程池成功");
        return executor;
    }

    @Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        //获取cpu核心数
        int i = Runtime.getRuntime().availableProcessors();
        //核心线程数
        int corePoolSize = i * 2;
        //最大线程数
        int maximumPoolSize = i * 2;
        //线程无引用存活时间
        long keepAliveTime = 60;
        //时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        //任务队列,接收一个整型的参数,这个整型参数指的是队列的长度,
        //ArrayBlockingQueue(int,boolean),boolean类型的参数是作为可重入锁的参数进行初始化,默认false,另外初始化了notEmpty、notFull两个信号量。
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(i * 2 * 10);
        //1. 同步阻塞队列 (put,take),直接提交。直接提交策略表示线程池不对任务进行缓存。新进任务直接提交给线程池,当线程池中没有空闲线程时,创建一个新的线程处理此任务。
        // 这种策略需要线程池具有无限增长的可能性。实现为:SynchronousQueue
        //2. 有界队列。当线程池中线程达到corePoolSize时,新进任务被放在队列里排队等待处理。有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,
        // 但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,
        // 但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,
        // CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
        //3. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。
        // 这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,
        // 适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

        //线程工厂
        //defaultThreadFactory()
        //返回用于创建新线程的默认线程工厂。
        //privilegedThreadFactory()
        //返回一个用于创建与当前线程具有相同权限的新线程的线程工厂。
        ThreadFactory threadFactory =Executors.defaultThreadFactory();
        //拒绝执行处理器
        RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
        //创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        return threadPoolExecutor;
    }
}

原文链接:https://blog.youkuaiyun.com/qq_44309610/article/details/113976210

下面演示ThreadPoolExecutor的实际使用


import java.io.Serializable;
import java.util.concurrent.*;

/**
 * desc: ThreadPoolExecutor demo
 *
 * @author qts
 * @date 2022/5/6 0006
 */
public class ThreadPoolTest {

    private static final int produceTaskSleepTime = 5;
    private static final int consumeTaskSleepTime = 5000;
    private static final int produceTaskMaxNumber = 20; //定义最大添加20个线程到线程池中

    public static void main(String[] args) {
        //构造一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
            TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
            new ThreadPoolExecutor.DiscardPolicy());
        try {
            for (int i = 1; i <= produceTaskMaxNumber; i++) {
                //一个任务,并将其加入到线程池
                String work = "work@ " + i;
                System.out.println("put :" + work);
                threadPool.execute(new ThreadPoolTask(work));
                //便于观察,等待一段时间
                Thread.sleep(produceTaskSleepTime);
            }
            // 关闭线程池
            threadPool.shutdown();

            // 等待所有线程执行完成
            // 方式一: 等待方式: 等所有线程执行完成,后再执行后面程序,等待时间定义了 1h
            //threadPool.awaitTermination(1, TimeUnit.HOURS);
            // 方式二: 循环判断方式: 判断是否终止,终止了就执行终止任务
            while(true){
                if(threadPool.isTerminated()){
                    // ...
                    System.out.println("所有的子线程都结束了!");
                    break;
                }
                Thread.sleep(1000);
            }
            
            System.out.println("线程池中启动的任务已全部完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 线程池执行的任务
     *
     * @author zhu
     */
    public static class ThreadPoolTask implements Runnable, Serializable {
        private static final long serialVersionUID = 0;
        //保存任务所需要的数据
        private Object threadPoolTaskData;

        ThreadPoolTask(Object works) {
            this.threadPoolTaskData = works;
        }

        @Override
        public void run() {
            //处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
            System.out.println("start------" + threadPoolTaskData);
            try {
                //便于观察,等待一段时间
                Thread.sleep(consumeTaskSleepTime);
                System.out.println("end------" + threadPoolTaskData);
            } catch (Exception e) {
                e.printStackTrace();
            }
            threadPoolTaskData = null;
        }

        public Object getTask() {
            return this.threadPoolTaskData;
        }
    }

}

@Bean
    public ThreadPoolExecutor threadPoolExecutor() {
        
        return new ThreadPoolExecutor(2,
            4,
            60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(10),
            new NamedThreadFactory("my-executor"),
            new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略:当前调用线程进行执行
    }

原文: https://blog.youkuaiyun.com/pineappleli/article/details/106697623

Java线程池ThreadPoolExecutorJava提供的一个用于管理和复用线程的工具类。它可以帮助我们更有效地管理线程资源,提高程序的性能和可维护性。 下面是一个简单的使用ThreadPoolExecutor的示例代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { // 创建一个线程池,其中包含5个线程 ExecutorService executor = Executors.newFixedThreadPool(5); // 提交任务给线程池执行 for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread("Task " + i); executor.execute(worker); } // 关闭线程池 executor.shutdown(); while (!executor.isTerminated()) { // 等待所有任务完成 } System.out.println("所有任务已完成"); } } class WorkerThread implements Runnable { private String taskName; public WorkerThread(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 开始执行任务:" + taskName); try { // 模拟任务执行时间 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 完成任务:" + taskName); } } ``` 上述代码中,首先通过`Executors.newFixedThreadPool(5)`创建了一个包含5个线程的线程池。然后使用`executor.execute(worker)`提交任务给线程池执行,其中`worker`是实现了`Runnable`接口的任务对象。任务会被线程池中的线程异步执行。 最后,通过`executor.shutdown()`关闭线程池,并使用`executor.isTerminated()`等待所有任务完成。完成后输出"所有任务已完成"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值