一、引言:

什么是池?

池(Pool),可以把它理解为一个容器,里面装着各种我们所需要的资源,我们需要资源的时候去这个容器里面拿,而不需要每次使用的时候去创建,从而达到一个复用的效果提高资源可利用率。

线程池的概念

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。简单点理解就是在需要使用多线程执行任务的时候不需要进行线程的创建,而是把任务提交到线程池中由线程池按照一定的规则处理。

简单线程池Demo


    public static void main(String[] args) {
        // 创建一个固定大小的线程池,大小为2
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        // 提交3个任务给线程池
        for (int i = 0; i < 3; i++) {
            Runnable task = new Task(i);
            executorService.submit(task);
        }
        // 所有任务执行完毕,关闭线程池   CSDN 骑电动车的小黄
        executorService.shutdown();
    }

    static class Task implements Runnable {
        private int taskId;
        public Task(int taskId) {
            this.taskId = taskId;
        }
        @Override
        public void run() {
            System.out.println("Task " + taskId + " 执行中...");
            try {
                // 暂停,
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task " + taskId + " 执行完毕");
        }
    }
执行结果
Task 1 执行中...
Task 0 执行中...
Task 0 执行完毕
Task 1 执行完毕
Task 2 执行中...
Task 2 执行完毕

二、创建线程池的几种方式

1、Executors类创建线程池

java.util.concurrent.Executors java自带的创建、管理线程池工具,可以简化线程池的创建和管理过程,提高性能和代码的可读性。提供了种创建方法:

1.1:newFixedThreadPool(int nThreads)

使用 newFixedThreadPool 创建一个固定为 nThreads 大小的线程池,假如我们创建一个大小为2的线程池提交了3个任务过去,那么就会有2个任务同时被执行,后面的任务会等前面的任务执行完按顺序执行。newFixedThreadPool创建线程的案例 ——> 简单线程池Demo

使用场景:

  • 提交的任务数量可控的情况下,能具体的预预估出任务数,并希望限制并发执行的任务数。
  • 资源限制,系统资源有限使用合理固定大小的线程池可以防止资源浪费的情况
  • 简化管理,只需要配置固定大小的线程数即可使用。

1.2:newCachedThreadPool()

创建可以缓存的线程池,有任务提交到线程池时如果有空闲的线程可用则立即使用空闲线程执行任务,如果没有空闲的线程可用就会创建一个新的线程执行任务,当空闲线程闲置一段时间(默认是60秒)之后还未被使用,那么就会进行销毁操作。

使用场景:

  • 不确定任务数量或者任务频率较高的情况下,并且期望能自动调整线程数量适应任务数量的变化时
  • 并发低、任务量较少,系统之间相对独立并发量比较低想降低对线程池的维护成本
  • 异步任务、短期任务例如IO型的任务可以相对较好的利用系统资源,减少维护成本又能提交任务效率

使用案例:

public static void main(String[] args) {
        // 创建可缓存的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 提交任务给线程池 CSDN 骑电动车的小黄
        for (int i = 0; i < 5; i++) {
            final int taskNumber = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Task " + taskNumber + " 执行中...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskNumber + " 执行完毕");
                }
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }

执行结果:

Task 0 执行中...
Task 1 执行中...
Task 2 执行中...
Task 3 执行中...
Task 4 执行中...
Task 3 执行完毕
Task 0 执行完毕
Task 4 执行完毕
Task 2 执行完毕
Task 1 执行完毕

1.3:newSingleThreadExecutor()

创建一个单线程的线程池,它只会使用一个线程执行任务,可以保证任务的执行顺序。

使用场景:

通常使用在任务需要按顺序执行但又不影响主线程执行,且任务之间存在依赖关系或者需要共享资源,保证任务在同一线程内执行避免线程安全问题。

使用案例:

public static void main(String[] args) {
        System.out.println("开始执行");
        // 创建单线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 提交任务给线程池   CSDN 骑电动车的小黄
        for (int i = 0; i < 5; i++) {
            final int taskNumber = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Task " + taskNumber + " 执行中...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskNumber + " 执行完毕");
                }
            });
        }
        // 关闭线程池
        executorService.shutdown();
        System.out.println("主线程执行完毕,等待线程池任务执行。");
    }

执行结果:

1.4:newScheduledThreadPool(int corePoolSize)

提供了任务调度功能与Spring提供的定时任务注解@Scheduled有相似之处,用于执行延迟性任务、周期性任务,corePoolSize指定线程池的核心线程数,除核心线程数之外当任务数过多时会根据需要创建额外的临时线程来执行任务,临时线程数最多不超过核心线程数。

案例:

    public static void main(String[] args) {
        // 创建具有固定核心线程数的线程池  CSDN 骑电动车的小黄
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

        // 延迟任务
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟任务开始执行....");
            }
        }, 3, TimeUnit.SECONDS);

        // 周期性任务,每隔1秒执行一次
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("周期性任务开始执行....");
            }
        }, 0, 1, TimeUnit.SECONDS);

        // 等待一段时间后关闭线程池
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.println("主线程执行完毕。。。");
    }

执行结果:

需要注意的是周期性任务的任务时间是由上一次任务的结束时间来进行计算的,而不是上一次任务的开始时间,比如我们在周期执行内暂停2秒,那么就会出现以下情况,上一次执行完之后马上开始下一次任务。

2、ForkJoinPool

java.util.concurrent.ForkJoinPool 也是java并发库中提供的一个线程池的实现,适用于可拆分的任务,提供了三种创建线程池的方法:

  1. ForkJoinPool()
  2. ForkJoinPool(int parallelism)
  3. ForkJoinPool.commonPool()

通常使用 ForkJoinPool.commonPool()  进行创建,它可以获取一个共享的 ForkJoinPool 实例,该实例由 JVM 自动管理并在需要时进行共享和重用,这个共享的线程池一般与处理器核心数保持一致。

案例:

    public static void main(String[] args) {
        // 获取共享的 ForkJoinPool 实例  CSDN 骑电动车的小黄
        ForkJoinPool commonPool = ForkJoinPool.commonPool();

        // 创建一个可拆分的任务
        RecursiveTask<Integer> task = new RecursiveTask<Integer>() {
            @Override
            protected Integer compute() {
                // 任务的计算逻辑
                Integer i = 1;
                System.out.println("执行了 ==>" + i);
                return i;
            }
        };

        // 提交任务到 commonPool 并获取结果
        Integer result = commonPool.invoke(task);
        result = result + 100;
        System.out.println("返回结果: " + result);
    }

执行结果:

3、ThreadPoolExecutor( 常用 )

java.util.concurrent.ThreadPoolExecutor 是 java 用于管理线程池和创建线程池的类,提供了比较灵活的方式来控制线程池中的数量、任务队列、拒绝策略,工作中使用频率也是最高的。

ThreadPoolExecutor(),中可自定义配置线程池个参数,使用情况可以参考 ==>  线程池的七个配置参数,很细!!!_骑电动车的小黄的博客-CSDN博客

使用案例:

public class PoolTest {

    public static void main(String[] args) {
        // 创建一个阻塞队列,用于存放等待执行的任务
        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);

        // 创建自定义的线程池,设置核心线程数、最大线程数、等待时间、时间单位、任务队列等参数
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue);

        // 提交任务给线程池,使用 execute() 方法提交
        for (int i = 0; i < 20; i++) {
            Runnable task = new MyTask(i);
            executor.execute(task);
        }

        // 关闭线程池
        executor.shutdown();
    }

}
class MyTask implements Runnable {
    private final int taskId;

    public MyTask(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public void run() {
        System.out.println("Task ID : " + taskId + " 执行 " + Thread.currentThread().getName());
    }
}

执行结果:

Task ID : 1 执行 pool-1-thread-2
Task ID : 3 执行 pool-1-thread-4
Task ID : 5 执行 pool-1-thread-4
Task ID : 0 执行 pool-1-thread-1
Task ID : 2 执行 pool-1-thread-3
Task ID : 9 执行 pool-1-thread-3
Task ID : 10 执行 pool-1-thread-3
Task ID : 8 执行 pool-1-thread-1
Task ID : 7 执行 pool-1-thread-4
Task ID : 16 执行 pool-1-thread-7
Task ID : 14 执行 pool-1-thread-7
Task ID : 6 执行 pool-1-thread-2
Task ID : 17 执行 pool-1-thread-8
Task ID : 12 执行 pool-1-thread-1
Task ID : 13 执行 pool-1-thread-4
Task ID : 11 执行 pool-1-thread-3
Task ID : 4 执行 pool-1-thread-5
Task ID : 15 执行 pool-1-thread-6
Task ID : 19 执行 pool-1-thread-10
Task ID : 18 执行 pool-1-thread-9

4、第三方工具包

比如使用 guava 包创建线程池,首先引入 guava 包。

        <!-- guava工具包-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.1-jre</version>
        </dependency>

创建线程池、提交任务

public class PoolTest {

    public static void main(String[] args) {
        // 使用 ThreadFactoryBuilder 创建自定义线程工厂
        ThreadFactoryBuilder threadFactoryBuilder = new ThreadFactoryBuilder()
                .setNameFormat("线程-%d")
                .setDaemon(true);
        ExecutorService executorService = Executors.newFixedThreadPool(5, threadFactoryBuilder.build());

        // 提交任务给线程池
        for (int i = 0; i < 10; i++) {
            Runnable task = new MyTask(i);
            executorService.execute(task);
        }

        // 关闭线程池
        executorService.shutdown();
    }

}
class MyTask implements Runnable {
    private final int taskId;

    public MyTask(int taskId) {
        this.taskId = taskId;
    }

    @Override
    public void run() {
        System.out.println("Task ID : " + taskId + " 执行 " + Thread.currentThread().getName());
    }
}

执行结果

Task ID : 0 执行 线程-0
Task ID : 2 执行 线程-2
Task ID : 1 执行 线程-1
Task ID : 3 执行 线程-3
Task ID : 4 执行 线程-4
Task ID : 5 执行 线程-1
Task ID : 6 执行 线程-0
Task ID : 8 执行 线程-1
Task ID : 7 执行 线程-3
Task ID : 9 执行 线程-0

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐