JAVA线程池

一,介绍

想象一下,你是一个忙碌的领导,手头有一堆任务要完成。你一个人怎么可能同时完成这么多任务呢?于是乎,你决定雇佣一支“Java线程池服务团队”。

这个团队里有很多个“小伙伴”(线程),他们每个人都有各自的技能和专长。当你有新的任务时,你只需要把任务交给团队负责人(线程池)就好了。

团队负责人(线程池)会根据任务的复杂度和优先级,选择合适的小伙伴(线程)来处理任务。有些任务可能很简单,只需要一个小伙伴就能搞定;有些任务可能很复杂,需要几个小伙伴共同协作。

Java线程池是一种用于管理和复用线程的机制,它可以在需要执行任务时,从线程池中获取一个空闲线程来执行任务,而不是每次都创建新的线程。线程池可以提高程序的性能和资源利用率,并且可以控制并发线程的数量,防止系统资源耗尽。

Java提供了java.util.concurrent包中的Executor框架来支持线程池的实现。常用的线程池实现类有以下几种:

1.FixedThreadPool

ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池,最多同时执行5个任务
executor.execute(new MyTask()); // 提交任务
executor.shutdown(); // 关闭线程池

2.CachedThreadPool

ExecutorService executor = Executors.newCachedThreadPool(); // 创建一个缓存线程池 executor.execute(new MyTask()); // 提交任务 executor.shutdown(); // 关闭线程池

3.SingleThreadExecutor

ExecutorService executor = Executors.newSingleThreadExecutor(); // 创建一个单线程线程池 executor.execute(new MyTask()); // 提交任务 executor.shutdown(); // 关闭线程池

4.ScheduledThreadPool

ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); // 创建一个定时任务线程池,最多同时执行3个任务
executor.schedule(new MyTask(), 1, TimeUnit.SECONDS); // 延迟1秒执行任务
executor.scheduleAtFixedRate(new MyTask(), 0, 1, TimeUnit.SECONDS); // 每隔1秒执行任务
executor.shutdown(); // 关闭线程池

以上是一些常用的线程池实现类,通过调用execute()方法或submit()方法可以向线程池提交任务。线程池会自动管理线程的创建、复用和回收,可以通过调整线程池的大小和配置来满足不同的需求。

使用线程池时需要注意合理设置线程池的大小,避免线程过多导致资源浪费,或线程过少导致任务堆积。同时,需要确保提交给线程池的任务是线程安全的,以避免并发访问的问题。

二,线程池参数介绍

线程池的参数可以用来配置线程池的大小、任务队列、线程的生存时间等,以满足不同场景下的需求。下面是线程池的一些常见参数及其介绍:

1.核心线程数(corePoolSize):

        核心线程数是线程池中保持活动状态的线程数量,即使这些线程是空闲的。核心线程会一直存活,除非设置了

allowCoreThreadTimeOut参数为true,则空闲一定时间后会被回收。默认情况下,核心线程数为0。

2.最大线程数(maximumPoolSize):

        最大线程数是线程池中允许的最大线程数量。当任务提交到线程池中,如果核心线程数还未达到上限,会创建新的核心线程来执行任务;如果核心线程数已达到上限,会将任务放入任务队列;如果任务队列已满,且当前线程数小于最大线程数,则会创建新的非核心线程来执行任务。默认情况下,最大线程数为Integer.MAX_VALUE。

3.任务队列(workQueue):

        任务队列用于保存等待执行的任务。线程池根据任务队列的类型和大小来管理待执行的任务。常见的任务队列类型有以下几种:

  1. SynchronousQueue
  2. LinkedBlockingQueue
  3. ArrayBlockingQueue

自定义队列:也可以根据具体需求实现自定义的任务队列。

4.线程存活时间(keepAliveTime):

        线程存活时间指的是当线程池中的线程数量超过核心线程数时,空闲线程的存活时间。超过存活时间后,空闲线程会被回收,直到线程池中的线程数量不超过核心线程数。默认情况下,线程存活时间为0,即空闲线程会立即被回收。

5.时间单位(unit):

        用于指定线程存活时间的单位,可以是纳秒、毫秒、秒等。

6.拒绝策略(rejectedExecutionHandler):

        当线程池和任务队列都已满,无法继续接受新的任务时,拒绝策略定义了如何处理被拒绝的任务。常见的拒绝策略有以下几种:

    • AbortPolicy
  • RejectedExecutionException
    • CallerRunsPolicy
    • DiscardPolicy
    • DiscardOldestPolicy

这些参数可以通过ThreadPoolExecutor类的构造方法或者Executors工具类提供的静态方法来创建线程池时进行配置。根据具体的需求,可以调整这些参数以达到最佳的线程池性能和资源利用率。

三,线程池底层原理

线程池的底层原理主要涉及线程的创建、管理和复用,以及任务的调度和执行。以下是线程池的基本底层原理:

1.线程池初始化:

        当创建一个线程池时,会初始化一定数量的线程作为核心线程,这些线程会一直存活,除非设置了允许核心线程超时回收。线程池还可以根据需要创建非核心线程,但数量不能超过最大线程数。

2.任务提交:

        当有任务提交到线程池时,线程池会根据当前线程数、任务队列状态和拒绝策略来决定任务的处理方式。如果当前线程数小于核心线程数,会创建新的核心线程来执行任务;如果当前线程数达到核心线程数,任务会被放入任务队列等待执行;如果任务队列已满,且当前线程数小于最大线程数,会创建新的非核心线程来执行任务;如果当前线程数达到最大线程数,且任务队列已满,根据拒绝策略来处理任务。

3.任务调度和执行:

        线程池会从任务队列中取出待执行的任务,并将任务分配给空闲的线程来执行。线程池通过线程调度算法(如先进先出、优先级等)来决定任务的执行顺序。执行任务时,线程池会调用任务的run()方法来执行具体的业务逻辑。

4.线程复用:

        当一个线程执行完任务后,它并不会被立即销毁,而是会继续等待执行新的任务。这样可以避免频繁地创建和销毁线程,提高线程的复用率和性能。

5.线程回收:

        线程池中的线程在空闲一定时间后,如果满足一定条件(如线程池大小超过核心线程数、空闲时间超过存活时间等),会被回收销毁,以节省系统资源。

6.异常处理:

        线程池会捕获并处理任务执行过程中抛出的异常,可以通过设置异常处理器(Thread.UncaughtExceptionHandler)来自定义异常处理逻辑。

线程池的底层实现一般使用ThreadPoolExecutor类,它是Executor接口的一个具体实现。ThreadPoolExecutor提供了丰富的配置选项,可以通过构造方法或者setXXX()方法来设置线程池的参数。Java提供的Executors工具类也提供了一些静态方法来创建不同类型的线程池,简化了线程池的创建和配置过程。

通过合理地配置线程池的参数,可以有效地管理线程的创建和复用,提高程序的性能和资源利用率。同时,线程池也可以控制并发线程的数量,避免系统资源耗尽和任务堆积的问题。

四,spring boot 整合Java多线程 异步/同步讲解与代码demo

下面是一个完整的 Spring Boot 项目结构,在该项目中实现了异步执行和同步执行,并使用了 Java 线程池:

1. 创建一个 Spring Boot 项目,包含以下文件:

```plaintext
- src/main/java/com/example/demo/
    - DemoApplication.java
    - config/
        - AsyncConfiguration.java
    - controller/
        - MyController.java
    - service/
        - MyService.java
    - task/
        - MyTask.java
```

2. 在 `DemoApplication.java` 文件中,作为 Spring Boot 项目的入口类:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3. 在 `AsyncConfiguration.java` 文件中,配置异步线程池:

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfiguration {
    private static final int CORE_POOL_SIZE = 10;
    private static final int MAX_POOL_SIZE = 20;
    private static final int QUEUE_CAPACITY = 200;

    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.initialize();
        return executor;
    }
}

4. 在 `MyController.java` 文件中,创建一个控制器类,用于接收请求并调用服务类:

package com.example.demo.controller;

import com.example.demo.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class MyController {
    @Autowired
    private MyService myService;

    @GetMapping("/sync")
    public String syncExecution() {
        myService.syncExecution();
        return "Sync Execution Done!";
    }

    @GetMapping("/async")
    public String asyncExecution() {
        myService.asyncExecution();
        return "Async Execution Started!";
    }
}

5. 在 `MyService.java` 文件中,创建一个服务类,用于处理具体的业务逻辑:

package com.example.demo.service;

import com.example.demo.task.MyTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class MyService {
    @Autowired
    private MyTask myTask;

    public void syncExecution() {
        myTask.execute(); // 同步执行任务
    }

    @Async
    public void asyncExecution() {
        myTask.execute(); // 异步执行任务
    }
}

6. 在 `MyTask.java` 文件中,创建一个任务类,具体定义需要执行的任务内容:

package com.example.demo.task;

import org.springframework.stereotype.Component;

@Component
public class MyTask {
    public void execute() {
        // 执行具体的任务逻辑
        try {
            Thread.sleep(2000); // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task Executed!");
    }
}

以上就是一个完整的 Spring Boot 项目结构,其中 `AsyncConfiguration.java` 配置了异步线程池,`MyController.java` 是控制器类,接收请求并调用服务类,`MyService.java` 是服务类,处理具体的业务逻辑,`MyTask.java` 是任务类,定义了需要执行的任务内容。在控制器类中,通过调用服务类的方法实现了异步执行和同步执行的功能。

@EnableAsync和@Async注解

`@EnableAsync` 是一个注解,用于启用 Spring 的异步方法支持。当在 Spring Boot 项目中使用 `@EnableAsync` 注解时,Spring 将会自动创建一个线程池,并使用该线程池来执行带有 `@Async` 注解的方法。

`@Async` 是一个方法级别的注解,用于标识一个方法是异步方法。当调用带有 `@Async` 注解的方法时,Spring 将会将该方法的执行委托给异步任务执行器(线程池)进行处理,而不会阻塞当前线程。

下面是对 `@EnableAsync` 和 `@Async` 的详细解释:

1. `@EnableAsync` 注解:
   - `@EnableAsync` 注解通常用于配置类上,表示开启异步方法支持。
   - 需要将 `@EnableAsync` 注解放置在 Spring Boot 项目的配置类上,以确保 Spring 能够正确地扫描和管理异步方法。
   - 启用异步方法支持后,Spring 将会自动创建一个线程池,用于处理异步方法的执行。

2. `@Async` 注解:
   - `@Async` 注解通常用于方法上,表示该方法是一个异步方法。
   - 在调用带有 `@Async` 注解的方法时,Spring 会将该方法的执行封装到一个独立的线程中,以实现异步执行。
   - 异步方法必须返回 `void` 或者 `Future` 对象(用于获取异步结果)。
   - 如果异步方法不需要返回结果,可以将返回类型设置为 `void`。
   - 如果异步方法需要返回结果,可以将返回类型设置为 `CompletableFuture` 或者 `ListenableFuture`。

通过使用 `@EnableAsync` 和 `@Async` 注解,可以实现在 Spring Boot 项目中使用异步方法来提升系统的并发能力和响应性能。在执行耗时操作时,将其封装成异步方法,可以避免阻塞主线程,提高系统的吞吐量和并发处理能力。

 异步和同步的好处和坏处

异步和同步是两种不同的执行方式,它们各有优缺点。下面是异步和同步的好处和坏处的解释:

异步的好处:
1. 提高系统性能:在异步执行中,可以将长时间运行的操作委托给其他线程或进程处理,同时主线程可以继续处理其他任务,从而提高系统的吞吐量和并发处理能力。
2. 提升用户体验:当执行耗时操作时,使用异步方式可以避免阻塞用户界面或用户请求的响应,提升用户体验和流畅度。
3. 资源利用率更高:异步执行可以充分利用系统资源,使得等待某些操作完成期间的其他资源得以利用,提高系统的资源利用效率。

异步的坏处:
1. 代码复杂性增加:异步执行需要进行线程之间的通信和同步控制,可能涉及到回调函数、Future 或 Promise 的使用,较为复杂,增加了代码的难度和维护成本。
2. 可能引起并发问题:异步执行涉及到多线程或多进程的操作,如果没有正确地处理共享数据的访问,可能会导致并发问题,如竞态条件、死锁等。
3. 调试和排错困难:由于异步执行中任务的执行顺序和执行时机是不确定的,当出现问题时,调试和排错会更加困难,需要使用适当的工具和技术。

同步的好处:
1. 代码简单清晰:同步执行按照代码的顺序依次执行,代码结构相对简单,易于理解和维护。
2. 编程模型直观:同步代码更符合人类的思维方式,按照预期的顺序执行,便于编写和调试。
3. 可靠性高:同步执行避免了并发访问共享数据的问题,能够保证数据的一致性和正确性。

同步的坏处:
1. 性能问题:同步执行会阻塞线程或进程的执行,如果某个操作较为耗时,会导致其他操作等待,从而降低系统的响应速度和吞吐量。
2. 用户体验差:由于同步执行可能会阻塞用户界面或用户请求的响应,长时间的等待会导致用户体验不佳,影响用户满意度。
3. 资源利用率低:同步执行时,等待时间未被充分利用,导致系统资源的浪费。

总的来说,异步执行适用于需要提高系统性能、增强用户体验和充分利用系统资源的场景,但会增加代码复杂性和并发问题的风险;同步执行适用于简单明确的业务场景,但可能存在性能问题和用户体验差的情况下。在实际开发中需根据具体场景选择合适的执行方式。

简单总结一下:

Java线程池是一种用于管理和复用线程的机制,可以提高程序的性能和资源利用率,并且可以控制并发线程的数量,防止系统资源耗尽。

常用的线程池实现类有FixedThreadPool、CachedThreadPool、SingleThreadExecutor和ScheduledThreadPool。

线程池的参数可以用来配置线程池的大小、任务队列、线程的生存时间等,以满足不同场景下的需求。

异步和同步是两种不同的执行方式,它们各有优缺点,需要根据具体的场景选择合适的执行方式。在实际开发中可以使用@EnableAsync和@Async注解来实现异步执行。

五,总结

Java线程池是一种多线程处理技术,它允许线程在池中重复使用,从而优化资源利用和提高性能。通过线程池,可以管理和控制线程的数量、生命周期和执行状态,提高应用程序的并发性和响应速度。线程池的主要特点包括:可重用线程、线程数量控制、任务队列、线程超时处理、线程执行状态监控等。在Java中,线程池是通过Executor框架提供的,其中包括ThreadPoolExecutor和ScheduledThreadPoolExecutor等实现类,可以通过这些实现类创建和管理线程池。使用Java线程池可以提高程序的可维护性、性能和健壮性,是开发高并发应用程序的重要技术之一。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐