Java线程池

Java线程池

在使用多线程的时候,通常我们直接通过new Thread()然后实现run方法即可执行多线程任务,但这种方法存在一些弊端。而使用线程池更具有以下优势:

  • 降低线程创建和销毁造成的系统开销,提高线程复用率
  • 提高系统响应速度,直接使用存在的线程,无需等待线程创建。
  • 方便线程并发数控制,若无限制创建线程,可能会出现OOM异常
  • 可扩展不同功能的线程池,如定时任务线程池,可控数量线程池

JDK自带的四种线程池

1.缓存线程池

newCachedThreadPool

创建一个线程池,该线程池根据需要创建新线程,但在以前构建的线程可用时将重用这些线程。这些池通常会提高执行许多短期异步任务的程序的性能。执行的调用将重用以前构造的线程(如果可用)。如果没有可用的现有线程,将创建一个新线程并将其添加到池中。已60秒未使用的线程将终止并从缓存中删除。因此,一个空闲足够长时间的池将不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有类似属性但不同详细信息(例如,超时参数)的池。返回:新建的线程池

以上是jdk源码注释

简单来讲,缓存线程池具有一定的复用性,但存在一个缓存时间,也就是如果有长时间未使用的线程,缓存线程池会自动去回收这个线程。而当线程池池中线程不足时,缓存线程池会自动的去创建新的线程。

  • 因此缓存线程池会优先使用线程池中目前存在的空闲线程,提高了线程的复用率

  • 同时当线程长时间未使用,也会自动去回收线程,避免了OOM

public class Pool {

    public static void main(String[] args) {



        ExecutorService executorService = Executors.newCachedThreadPool();


        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在执行工作");
        });

        //模拟延迟
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在执行工作");
            //模拟延迟
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在执行工作");
        });

    }

}

pool-1-thread-1正在执行工作
pool-1-thread-1正在执行工作
pool-1-thread-2正在执行工作

2.固定线程池

newFixedThreadPool

创建一个线程池,该线程池重用在共享的无边界队列中操作的固定数量的线程。在任何时候,最多nThreads线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到有线程可用为止。如果任何线程在关闭前的执行过程中因故障而终止,则在需要执行后续任务时,将替换一个新线程。池中的线程将一直存在,直到显式关闭为止。

以上是jdk源码注释

固定线程池如同其名,线程池本身线程数量是确定下来的,并且这些线程一旦创建不会被自动销毁而是处于一种等待任务的状态。当该线程池中所有线程都处于活跃状态时,如果有新的任务提交,则需要将任务放在等待队列中,直到有线程可用为止。

  • 提高了线程复用率,无需重新创建销毁线程(除非执行任务过程中线程出现了异常)
  • 固定线程数量,且天然具有队列,有一定的任务囤积能力,避免了OOM
public class Pool {

    public static void main(String[] args) {



        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 11; i++) {

            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName()+"正在工作");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

    }

}

pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-2正在工作
pool-1-thread-4正在工作
pool-1-thread-5正在工作
pool-1-thread-6正在工作
pool-1-thread-7正在工作
pool-1-thread-8正在工作
pool-1-thread-9正在工作
pool-1-thread-10正在工作
pool-1-thread-8正在工作

3.计划线程池

newScheduledThreadPool

创建一个线程池,该线程池可以安排命令在给定延迟后运行或定期执行。

以上是jdk源码注释

计划线程池提供了一组线程池来执行定时或是延时任务,当需要执行任务时,会从线程池中获取空闲的线程执行当前任务,且每次都随机抽取线程使用。

  • schedule 延时任务方法,需要提供一个Runnable,一个延时数,一个时间单位
  • scheduleAtFixedRate 定时间隔任务方法,需要提供一个Runnable,一个定时间隔数,一个时间单位
  • scheduleWithFixedDelay 固定延时间隔任务方法,需要提供一个Runnable,一个延时数,一个时间单位

前一个schedule可以类比js的setTimeout,后面的两个方法有点像setInterval,但是前者基于固定的时间间隔,后者基于当前任务与上一个任务的时间间隔

public class Pool {

    public static void main(String[] args) {


        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

        scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"延时任务1");
            }
        }, 1000,TimeUnit.MILLISECONDS);

        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"定时任务1");
            }
        },0,1000,TimeUnit.MILLISECONDS);


        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"定时任务2");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },0,1000,TimeUnit.MILLISECONDS);

    }

}

pool-1-thread-1定时任务1
pool-1-thread-1定时任务2
pool-1-thread-1延时任务1
pool-1-thread-1定时任务1
pool-1-thread-1定时任务1
pool-1-thread-1定时任务1
pool-1-thread-1定时任务2

4.单线程池

newSingleThreadExecutor

创建一个执行器,该执行器使用在无边界队列中操作的单个工作线程。(但请注意,如果此单个线程在关闭前的执行过程中因故障而终止,则在需要执行后续任务时,将替换一个新线程。)任务保证按顺序执行,并且在任何给定时间活动的任务都不会超过一个。与其他等效的newFixedThreadPool(1)不同,返回的执行器保证不会重新配置为使用其他线程。返回:新创建的单线程执行器

以上是jdk源码注释

单线程池保证了线程池中只使用一个线程来服务所有提交到该线程池的任务,如果该线程正在活跃,则新来的任务需要被囤积在队里中,等待该线程处理。当该线程执行发生异常,会在下一次执行任务时重新创建线程。

  • 保证了每次只用一个线程来执行任务,避免了OOM
  • 维护了一个线程,无需重复创建销毁线程,提高了系统速度
public class Pool {

    public static void main(String[] args) {


        ExecutorService executorService = Executors.newSingleThreadExecutor();



        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在工作");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });


        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName()+"正在工作");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

}

pool-1-thread-1正在工作
pool-1-thread-1正在工作

总结

  • 固定线程池:适用于并发任务数已知且稳定的场景。如果应用需要处理一组固定数量的任务,可以选择这个线程池。
  • 缓存线程池:适用于执行时间较短的任务。如果应用需要动态创建线程以处理临时性任务,可以选择这个线程池
  • 单线程池:适用于需要按顺序执行任务的场景。如果应用需要保证任务按照指定顺序执行,例如日志记录等,可以选择这个线程池。
  • 计划线程池:支持定时和周期性任务的执行。如果应用需要定期执行任务,可以选择这个线程池。

线程池核心参数

七个核心参数

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

1.核心线程池大小

corePoolSize 即线程池中至少会保留的线程数量

2.最大线程大小

maximumPoolSize 池中允许的最大线程数

3.空闲线程存活时间

keepAliveTime 大于核心线程数时的空闲线程保持的存活时间

4.空闲线程存活时间单位

unit

5.工作队列

workQueue 当有任务进入线程池时会先进入工作队列等待线程调度

有以下四种工作队列

ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

底层基于数组实现的队列的数据结构

LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

底层基于链表实现的数据结构

无缓冲等待队列 SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

底层是基于栈实现的非公平队列,如果传参true,可以使用基于队列实现的公平队列

SynchronousQueue是一种特殊的阻塞队列,队列长度是0,一个线程往队列放数据,必须等待另一个线程取走数据。同样,一个线程从队列中取数据,必须等待另一个线程往队列中放数据。

PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

6.线程工厂

threadFactory 创建一个新线程时使用的工厂,可以用来设定线程名、是否为守护线程等等

7.拒绝策略

当工作队列已满且线程池活跃线程已经达到线程池最大线程数量时,如果有新的任务进入则会除法拒绝策略。

呼叫者运行策略 CallerRunsPolicy

该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经关闭,则直接抛弃任务。哪里来的回到哪里去、

new ThreadPoolExecutor.CallerRunsPolicy()

终止策略 AbortPolicy

该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。

new ThreadPoolExecutor.AbortPolicy()

丢弃策略 DiscardPolicy

该策略下,直接丢弃任务,什么都不做。

new ThreadPoolExecutor.DiscardPolicy()

丢弃最旧策略 DiscardOldestPolicy

该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列。 (抛弃队头元素,新任务放入队尾)

new ThreadPoolExecutor.DiscardOldestPolicy()

SpringBoot线程池

ThrealPoolTaskExecutor

  • Spring框架提供的线程池实现,具有更高的灵活性。
  • 可以配置线程池的各项参数,如核心线程数、最大线程数、队列容量等。
@Configuration
public class ThreadPoolTaskConfig implements AsyncConfigurer {

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor TaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setQueueCapacity(20);
        taskExecutor.setThreadNamePrefix("");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        // 线程池对拒绝任务的处理策略
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 初始化
        taskExecutor.initialize();
        return taskExecutor;
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:Java线程池

字数:3.1k

本文作者:Os467

发布时间:2024-03-13, 17:15:23

最后更新:2024-03-14, 22:02:02

原始链接:https://os467.github.io/2024/03/13/java%E7%BA%BF%E7%A8%8B%E6%B1%A0/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏