Java 线程池规范

目录

    • 1、为什么要使用线程池?
    • 2、线程池如何命名
    • 3、不要使用 Executors 下自带线程池
    • 4、@Async 正确用法
    • 5、项目中可以定义多个线程池吗?

1、为什么要使用线程池?

在使用多线程时,频繁地创建和销毁线程会带来显著的性能开销。使用线程池可以避免这种开销,并提供了以下优点:

  • 重用线程:线程池会维护一组可重用的线程,避免了频繁地创建和销毁线程的开销。
  • 控制并发度:线程池允许我们限制并发执行任务的数量,避免系统过载和资源耗尽。
  • 任务排队:线程池提供了任务队列,可以将任务安排在队列中,并按照指定的调度策略顺序执行。
  • 统一管理:线程池提供了统一的管理和监控接口,可以方便地管理线程的生命周期和执行状态。

2、线程池如何命名

在线程池中,给线程命名是一项良好的实践,有助于追踪和排查问题。通过为每个线程设置有意义的名称,可以快速定位具体的任务或线程,并进行更有效的日志追踪,可以根据自己公司对线程池的规范来命名。

Java中的线程池通常使用ThreadPoolExecutor类来创建和管理。可通过ThreadFactory参数自定义创建线程的工厂,进而实现为线程设置名称。下面是一个示例:

public static void main(String[] args) {
		// 工单线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5, // 核心线程数
                10, // 最大线程数
                1, TimeUnit.MINUTES, // 线程空闲超时时间
                new LinkedBlockingQueue<>(2000), // 等待队列
                new ThreadFactory() {
                    private final AtomicInteger poolNumber = new AtomicInteger(1);
                    private final AtomicInteger threadNumber = new AtomicInteger(1);
                    private final ThreadGroup group = new ThreadGroup("OrderThreadPool-" + poolNumber.getAndIncrement());

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(group, r, group.getName()+"-Thread-" + threadNumber.getAndIncrement());
                        return t;
                    }
                }
        );

        for(int i=0;i<7;i++){
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName());
                // 具体的任务代码
            });
        }
    }

打印:

OrderThreadPool-1-Thread-1
OrderThreadPool-1-Thread-5
OrderThreadPool-1-Thread-4
OrderThreadPool-1-Thread-2
OrderThreadPool-1-Thread-3
OrderThreadPool-1-Thread-5
OrderThreadPool-1-Thread-1

3、不要使用 Executors 下自带线程池

Java并发库(JUC)提供了几个预定义的线程池,如Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor()等。虽然这些线程池在简单场景下很方便,但在某些情况下,它们可能会引发性能问题。

这些预定义的线程池具有一些缺点:

  • 固定大小线程池:newFixedThreadPool()和newSingleThreadExecutor()创建的线程池大小是固定的,线程数不会根据需要进行调整,可能会导致线程资源的浪费或不足。
  • 使用无界队列:newFixedThreadPool()和newCachedThreadPool()使用无界队列来存储等待执行的任务,如果任务提交速度超过处理速度,可能会导致内存溢出。
  • 使用默认线程工厂和异常处理器:上述线程池使用默认的线程工厂和异常处理器,可能无法满足特定需求,如自定义线程名称、处理异常等。

因此,在实际开发中,尽量避免使用Executors自带的四个线程池,而是根据需求手动创建ThreadPoolExecutor,以便更好地管理和配置线程池。

4、@Async 正确用法

在Spring Boot中,@Async注解基于动态代理,如果不指定要使用的线程池,会默认使用Spring Boot提供的线程池,并且没有等待队列的限制,这可能导致等待队列无限大导致OOM,因此需要声明一个线程池。

首先,在Spring配置类上添加@EnableAsync注解,以启用异步方法的支持:

@Configuration
@EnableAsync
public class AppConfig {
    // 配置其他的Bean或组件
}

接下来,创建一个线程池的配置类,用来配置自定义的线程池:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class ExecutorConfig implements AsyncConfigurer {

    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 核心线程数
        executor.setMaxPoolSize(20); // 最大线程数
        executor.setQueueCapacity(50); // 等待队列大小
        executor.setThreadNamePrefix("MyAsyncThread-"); // 线程名前缀
        executor.initialize();
        return executor;
    }

}

最后,在需要异步执行的方法上添加@Async注解,并指定要使用的线程池名称:

@Service
public class MyService {
    @Async("taskExecutor")
    public void asyncMethod() {
        // 异步方法的具体实现
    }
}

5、项目中可以定义多个线程池吗?

可以,一般项目中会用到多个线程池,根据不同业务定义不同线程池,可以通过 static 或者交给 spring 管理都是可以的。

你可能感兴趣的:(Java,java,开发语言,线程池,Thread)