Java并发编程之线程池实现原理详解

 更新时间:2023年05月23日 14:13:48   作者:越走越远的风  
池化思想是一种空间换时间的思想,期望使用预先创建好的对象来减少频繁创建对象的性能开销,java中有多种池化思想的应用,例如:数据库连接池、线程池等,下面就来具体讲讲

前言

池化思想是一种空间换时间的思想,期望使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一管理,减少对象使用成本。

java中有多种池化思想的应用,例如:数据库连接池、线程池、字符串常量池等。

为什么使用线程池

频繁的开启线程或者停止线程,线程需要重新被cpu从就绪到运行状态调度,需要发生cpu的上下文切换,效率非常低。

线程池作用

  • 降低线程创建和销毁的开销:通过线程池重用已经创建的线程,可以避免频繁创建和销毁线程所造成的内存和CPU资源开销。
  • 提高线程执行效率:线程池中的线程是经过优化的,通常会采用更少的线程、更高效的调度算法、更快的执行速度等方式,以提高线程的执行效率。
  • 降低线程间竞争的激烈程度:线程池中的线程数量是有限的,可以减少线程间竞争的激烈程度,从而降低CPU资源的消耗。
  • 提高应用程序的可伸缩性和健壮性:线程池可以控制线程的数量和执行速度,可以更好地满足应用程序的需求,从而提高应用程序的可伸缩性和健壮性。

ThreadPoolExecutor参数

  • int corePoolSize: 核心线程数
  • int maximumPoolSize: 最大线程数
  • long keepAliveTime: 超出corePoolSize后创建的线程的存活时间
  • TimeUnit unit: keepAliveTime的时间单位
  • BlockingQueue workQueue: 任务队列,存放待执行任务
  • ThreadFactory threadFactory: 创建线程的线程工厂
  • RejectedExecutionHandler handler: 拒绝策略

当线程数小于核心线程数时,创建线程。

当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

当线程数大于等于核心线程数,且任务队列已满时:

  • 1)若线程数小于最大线程数,创建线程
  • 2)若线程数等于最大线程数,执行拒绝策略

拒绝策略

当线程池中的线程数量达到最大值或者任务队列已满时,如果再有新的任务提交给线程池,线程池会拒绝接受新的任务。这时,线程池会采用一定的拒绝策略来处理这些被拒绝的任务。

Java中提供了四种拒绝策略:

  • AbortPolicy:默认策略,直接抛出RejectedExecutionException异常,表示任务被拒绝执行。
  • CallerRunsPolicy:这个策略会使用当前客户端线程来执行任务。如果客户端线程不够,或者存在线程被阻塞,则仍然会抛出RejectedExecutionException异常。
  • DiscardPolicy:这个策略会直接丢弃被拒绝的任务,不会执行任何操作。
  • DiscardOldestPolicy:这个策略会丢弃队列中等待时间最长的任务,然后执行当前被提交的任务。如果队列中没有等待时间最长的任务,则会使用CallerRunsPolicy策略来处理被拒绝的任务。

也可以自定义拒绝策略,实现RejectedExecutionHandler接口即可。

建议自定义实现拒绝策略,将任务持久化到db,后期在手动补偿。

线程池的创建方式

Executors为我们提供了四种新建线程池的方式:

newCachedThreadPool()可缓存线程池

public static ExecutorService newCachedThreadPool() {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                    60L, TimeUnit.SECONDS,  
                                    new SynchronousQueue<Runnable>());  
}

线程池是创建一个核心线程数为0,最大线程为Inter.MAX_VALUE的线程池,线程池数量不确定,有空闲线程则优先使用,没用则创建新的线程处理任务,处理完放入线程池。

newFixedThreadPool(): 可定长度,限制最大线程数

public static ExecutorService newFixedThreadPool(int nThreads) {  
    return new ThreadPoolExecutor(nThreads, nThreads,  
                                    0L, TimeUnit.MILLISECONDS,  
                                    new LinkedBlockingQueue<Runnable>());  
}

创建一个核心线程数跟最大线程数相同的线程池,线程池数量大小不变,如果有任务放入队列,等待空闲线程。

newScheduledThreadPool(): 可定时线程池\

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  
    return new ScheduledThreadPoolExecutor(corePoolSize);  
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  
    return new ScheduledThreadPoolExecutor(corePoolSize);  
}
public ScheduledThreadPoolExecutor(int corePoolSize) {  
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,  
        new DelayedWorkQueue());  
}

创建一个没有最大线程数限制的可以定时执行线程池,还有创建一个只有单个线程的可以定时执行线程池(Executors.newSingleThreadScheduledExecutor())

newSingleThreadExecutor(): 单线程 线程池

public static ExecutorService newSingleThreadExecutor() {  
    return new FinalizableDelegatedExecutorService  
        (new ThreadPoolExecutor(1, 1,  
                                0L, TimeUnit.MILLISECONDS,  
                                new LinkedBlockingQueue<Runnable>()));  
}

池里只有一个线程

这四种底层都是基于ThreadPoolExecutor构造函数封装,且都采用的无界队列,使用时需注意防止内存溢出。

自定义线程名称

可以通过自定义ThreadFactory来为线程池中的线程指定名称。

ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("custom-thread-%d").build();

线程池的五种状态

        // runState is stored in the high-order bits
        private static final int RUNNING    = -1 << COUNT_BITS;
        private static final int SHUTDOWN   =  0 << COUNT_BITS;
        private static final int STOP       =  1 << COUNT_BITS;
        private static final int TIDYING    =  2 << COUNT_BITS;
        private static final int TERMINATED =  3 << COUNT_BITS;
  • RUNNING:线程池运行状态,此时线程池中的任务队列可能有等待中的任务,但线程池会持续从队列中取出任务执行。
  • SHUTDOWN:线程池关闭状态,此时线程池会拒绝接受新的任务,但会执行完已经排队的任务,不接受新任务并不意味着已经排队的任务必须执行完。
  • STOP:线程池强制停止状态,此时线程池不仅会拒绝接受新的任务,而且会中断正在执行的任务,并终止线程池。
  • TIDYING:线程池在转换为终止状态时的一种特殊状态,此时线程池会执行钩子方法terminated(),并等待所有任务执行完成。
  • TERMINATED:线程池终止状态,此时线程池中的所有任务已经执行完成,线程池被彻底终止。

线程数设置

  • 核心线程数:线程池中始终存在的线程数量。当任务被提交到线程池时,如果当前运行的线程少于核心线程数,则会创建一个新的线程来执行该任务,即使其他的核心线程正在空闲状态。因此,核心线程数通常应该设置为预期的并发数。
  • 最大线程数:允许线程池中同时存在的最大线程数量。当线程池中的线程数量达到最大线程数时,后续提交到线程池中的任务将被暂存到任务队列中等待处理。因此,最大线程数不应该设置过高,否则可能会导致系统资源紧张。

合理地设置核心线程数和最大线程数可以优化线程池的性能和响应时间。下面是一些设置建议:

  • 核心线程数 = CPU核心数 + 1
  • 最大线程数 = 核心线程数 * 2
  • 如果任务执行时间较长,可以适当增加最大线程数,以避免任务堆积在队列中无法及时处理

具体的设置需要根据实际情况来考虑,如果线程池主要执行的是I/O密集型任务,可以适当增加核心线程数和最大线程数,以充分利用系统资源。如果线程池主要执行的是CPU密集型任务,则需要根据系统的CPU核心数来设置核心线程数和最大线程数,避免过度消耗CPU资源。

springboot集成线程池

package com.fandf.common.config;  
import com.fandf.common.utils.CustomThreadPoolTaskExecutor;  
import lombok.Getter;  
import lombok.Setter;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.context.annotation.Bean;  
import org.springframework.core.task.TaskExecutor;  
import org.springframework.scheduling.annotation.EnableAsync;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;  
import java.util.concurrent.ThreadPoolExecutor;  
/**  
* @author fandongfeng   
*/  
@EnableAsync(proxyTargetClass = true) 
@Configuration
public class DefaultAsycTaskConfig {  
    /**  
    * 线程池维护线程的最小数量.  
    */  
    @Value("${asyc-task.corePoolSize:10}")  
    private int corePoolSize;  
    /**  
    * 线程池维护线程的最大数量  
    */  
    @Value("${asyc-task.maxPoolSize:200}")  
    private int maxPoolSize;  
    /**  
    * 队列最大长度  
    */  
    @Value("${asyc-task.queueCapacity:10000}")  
    private int queueCapacity;  
    /**  
    * 线程池前缀  
    */  
    @Value("${asyc-task.threadNamePrefix:FdfExecutor-}")  
    private String threadNamePrefix;  
    @Bean  
    public TaskExecutor taskExecutor() {  
        ThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();  
        executor.setCorePoolSize(corePoolSize);  
        executor.setMaxPoolSize(maxPoolSize);  
        executor.setQueueCapacity(queueCapacity);  
        executor.setThreadNamePrefix(threadNamePrefix);  
        /*  
        rejection-policy:当pool已经达到max size的时候,如何处理新任务  
        CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行  
        */  
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  
        executor.initialize();  
        return executor;  
    }  
}

使用

@Service
public class MyService {
    @Async("taskExecutor")
    public void doSomething() {
        // 异步执行的任务
    }
}

到此这篇关于Java并发编程之线程池实现原理详解的文章就介绍到这了,更多相关Java线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Spring boot + jQuery上传文件(kotlin)功能实例详解

    使用Spring boot + jQuery上传文件(kotlin)功能实例详解

    本文通过实例代码给大家介绍了使用Spring boot + jQuery上传文件(kotlin) 功能,需要的朋友可以参考下
    2017-07-07
  • SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程

    SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程

    SpringDoc OpenAPI 是一个强大的工具,能够帮助我们轻松生成 OpenAPI 3.0 规范的文档,并提供交互式的 Swagger UI 界面,所以本文给大家介绍了SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程,需要的朋友可以参考下
    2024-07-07
  • SpringBoot中缓存注解的使用详解

    SpringBoot中缓存注解的使用详解

    为了实现缓存,Spring Boot 提供了一些缓存注解,可以方便地实现缓存功能,这篇文章主要介绍了SpringBoot中常用的缓存注解的使用方法,需要的可以参考一下
    2023-06-06
  • spring bean标签中的init-method和destroy-method详解

    spring bean标签中的init-method和destroy-method详解

    这篇文章主要介绍了spring bean标签中的init-method和destroy-method,在很多项目中,经常在xml配置文件中看到init-method 或者 destroy-method ,因此整理收集下,方便以后参考和学习,需要的朋友可以参考下
    2023-04-04
  • springboot处理异常的5种方式

    springboot处理异常的5种方式

    本文主要介绍了springboot处理异常的5种方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • 浅谈Java多进程程序的运行模式

    浅谈Java多进程程序的运行模式

    这篇文章主要介绍了浅谈Java多进程程序的运行模式,包括对进程阻塞问题的讨论等,需要的朋友可以参考下
    2015-11-11
  • SpringBoot中的@CacheEvict 注解的实现

    SpringBoot中的@CacheEvict 注解的实现

    本文主要介绍了SpringBoot中的@CacheEvict注解的实现,@CacheEvict 注解用于清空缓存,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2024-03-03
  • 在java中使用SPI创建可扩展的应用程序操作

    在java中使用SPI创建可扩展的应用程序操作

    这篇文章主要介绍了在java中使用SPI创建可扩展的应用程序操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • Java如何在List或Map遍历过程中删除元素

    Java如何在List或Map遍历过程中删除元素

    相信大家在日常的开发过程中,经常需要对List或Map里面的符合某种业务的数据进行删除,但是如果不了解里面的机制就容易掉入“陷阱”导致遗漏或者程序异常。下面这篇文章将会给大家详细介绍Java如何在List和Map遍历过程中删除元素,有需要的朋友们可以参考借鉴。
    2016-12-12
  • Springboot 读取 yml 配置文件里的参数值

    Springboot 读取 yml 配置文件里的参数值

    本文主要介绍了Springboot 读取 yml 配置文件里的参数值,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12

最新评论