常见的线程类型包括用户线程(User Threads)、守护线程(Daemon Threads)、主线程(Main Thread)、工作线程(Worker Threads)和线程池中的线程。
一、用户线程(User Threads)
特点:
- 用户线程是普通的Java线程,通常由程序员显式创建。
- 用户线程在程序运行期间一直存在,直到它们完成任务或程序终止。
代码示例:
public class UserThreadExample {
public static void main(String[] args) {
Thread userThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("User thread running: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
userThread.start();
}
}
2、守护线程(Daemon Threads)
特点:
- 守护线程是在后台运行的线程,它们通常用于为其他线程提供服务。
- 当所有用户线程都结束时,JVM会自动终止守护线程并退出。(即使守护线程没有执行完任务,也会被JVM强制退出,所以主要业务一定不能放在守护线程中执行)
- 守护线程通常用于执行一些辅助任务,如垃圾回收、日志记录等。
代码示例:
public class DaemonThreadExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon thread running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 设置为守护线程,就这一句就够了
daemonThread.start();
// 主线程休眠一段时间后结束
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
解释:
- 创建一个新的线程daemonThread,并将其设置为守护线程。
- 守护线程无限循环运行,每秒打印一条消息。
- 主线程休眠5秒后结束,此时JVM会自动终止,守护线程也会被终止。
3、主线程(Main Thread)
特点:
- 主线程是每个Java应用程序的入口点,它是JVM在启动时自动创建的第一个线程。
- 主线程负责执行main方法中的代码。
- 当主线程结束时,如果还有其他用户线程在运行,JVM会继续运行这些线程。
代码示例:
public class MainThreadExample {
public static void main(String[] args) {
System.out.println("Main thread started");
Thread userThread = new Thread(() -> {
System.out.println("User thread running...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
userThread.start(); // 用户线程启动,实际上就是主线程干的事情
System.out.println("Main thread ending");
}
}
解释:
- 主线程执行main方法中的代码。
- 创建并启动一个用户线程userThread。
- 主线程在启动用户线程后继续执行打印,之后就结束了。
- 用户线程会继续运行,直到完成任务。
4、工作线程(Worker Threads)
特点:
- 工作线程通常用于执行具体的任务,它们可以是用户线程或守护线程。
- 工作线程通常由线程池管理,用于处理任务队列中的任务。
- 工作线程可以提高应用程序的并发性和性能。
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WorkerThreadExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2); // 创建固定大小的线程池
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown(); // 关闭线程池
}
}
解释:
- 创建一个固定大小为2的线程池executor。
- 提交5个任务到线程池,每个任务在执行时会打印任务ID和当前线程名称。
- 调用shutdown方法关闭线程池,等待所有任务完成。
5、线程池中的线程
特点:
- 线程池中的线程是由线程池管理的,它们可以重复使用,减少了创建和销毁线程的开销。
- 线程池可以控制并发线程的数量,避免过多的线程消耗系统资源。
- 线程池通常用于处理大量的短期任务。
代码示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool(); // 创建可缓存线程池
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown(); // 关闭线程池
}
}
解释:
- 创建一个可缓存线程池executor,它会根据需要创建新的线程。
- 提交10个任务到线程池,每个任务在执行时会打印任务ID和当前线程名称。
- 调用shutdown方法关闭线程池,等待所有任务完成。
6、线程分类总结
(1)、用户线程:普通的Java线程,由程序员显式创建。
(2)、守护线程:后台线程,用于提供服务,当所有用户线程结束时自动终止。
(3)、主线程:JVM自动创建的第一个线程,执行main
方法中的代码。
(4)、工作线程:用于执行具体任务的线程,通常由线程池管理。
(5)、线程池中的线程:由线程池管理的线程,可以重复使用,减少开销。
7、springboot常见线程类型
(1)、主线程(Main Thread)
1、概述
- 这是Spring Boot应用启动时由JVM创建的第一个线程。
- 主线程负责启动Spring Boot应用,加载配置,初始化Spring容器等。
2、创建时机
- 应用启动时,由JVM自动创建。
代码示例
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
(2)、Tomcat/NIO线程(接受客户端请求用)
1、概述
- 如果你的Spring Boot应用使用了嵌入式的Tomcat服务器,Tomcat会创建多个线程来处理HTTP请求。
- 这些线程通常是NIO线程(非阻塞线程,可异步),负责接收客户端请求并处理响应。
2、创建时机
- 当Spring Boot应用启动并初始化嵌入式Tomcat服务器时。
配置文件示例
无需编码,仅配置即可,不配置也可以会有默认配置。
server:
port: 8080
tomcat:
max-threads: 200 # 最大线程数
min-spare-threads: 10 # 最小空闲线程数
(3)、定时任务线程(Scheduled Tasks)
1、概述
- 如果你在Spring Boot应用中使用了定时任务(如@Scheduled注解),Spring Boot会创建一个或多个线程来执行这些定时任务。
- 这些线程通常由TaskScheduler管理。
2、创建时机
- 当Spring Boot应用启动并初始化定时任务时。
代码示例
@Configuration
@EnableScheduling
public class SchedulerConfig {
}
@Component
public class ScheduledTasks {
@Scheduled(fixedRate = 5000) // 自动创建定时任务线程监听并执行
public void performTask() {
System.out.println("Executing scheduled task at " + LocalDateTime.now());
}
}
(4)、异步任务线程(Async Tasks)
1、概述
- 如果你在Spring Boot应用中使用了异步方法(如@Async注解),Spring Boot会创建一个或多个线程来执行这些异步任务。
- 这些线程通常由TaskExecutor管理。
这个在附录中在详细说明。
(5)、自定义线程
1、概述
- 你可以在Spring Boot应用中手动创建和管理线程,例如通过实现Runnable接口或继承Thread类。
2、创建时机
- 在你需要的时候手动创建。
代码示例
@Component
public class CustomThreadExample {
@PostConstruct
public void startCustomThread() {
Thread customThread = new Thread(() -> {
while (true) {
System.out.println("Custom thread running at " + LocalDateTime.now());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customThread.start();
}
}
附录:
异步任务线程(Async Tasks)
1、概述
- 如果你在Spring Boot应用中使用了异步方法(如@Async注解),Spring Boot会创建一个或多个线程来执行这些异步任务,主线程不会阻塞会继续执行后面的代码。
- 这些线程通常由TaskExecutor管理。
- 使用异步任务,使用@EnableAsync开启后(主方法等配置入口),在需要的方法上添加@Async注解即可。
2、异步任务线程的作用
(1)、提高响应性
- 异步任务线程可以在后台执行耗时的操作,而不阻塞主线程或请求处理线程。这样,主线程可以继续处理其他请求,提高系统的整体响应性。
(2)、优化资源利用
- 对于耗时较长的任务,如果使用同步方式处理,可能会占用宝贵的线程资源,导致其他任务无法及时处理。异步任务线程可以将这些任务移到后台执行,释放主线程资源。
(3)、并发处理
- 异步任务线程可以并行处理多个任务,提高系统的并发处理能力。这对于需要处理大量并发请求的应用尤其重要。
3、异步任务特点
(1)、异步任务线程:异步任务在单独的线程中执行,不会阻塞主线程。
(2)、立即返回:异步方法调用会立即返回一个CompletableFuture
对象,主线程可以继续执行其他任务。
(3)、回调机制:使用thenAccept
等方法注册回调函数,这些回调函数在异步任务完成时被调用(被线程池的其他线程调用),不会阻塞主线程。
4、常见的异步任务场景
(1)、邮件发送
- 发送邮件是一个耗时操作,通常需要几秒钟甚至更长时间。使用异步任务线程可以在后台发送邮件,而不会阻塞主线程。
(2)、文件上传和下载
- 文件上传和下载操作通常涉及大量的I/O操作,使用异步任务线程可以避免阻塞主线程,提高系统的响应性。
(3)、数据处理和计算
- 对于复杂的数据处理和计算任务,使用异步任务线程可以将这些任务移到后台执行,释放主线程资源。
(4)日志记录
- 日志记录操作通常需要写入磁盘,使用异步任务线程可以避免阻塞主线程,提高系统的性能。
5、异步任务线程与异步请求处理的区别
前者是优化请求响应速度,把不重要或耗时的事情放在后台慢慢执行,主线程可以继续干其他事情或直接返回的一种处理方式;后者是并行可同时接受客户端的多个请求,两者完全不是一回事,需要注意下。
(1)、异步任务线程
- 用于处理耗时较长的后台任务。
- 通常由开发人员手动创建和管理。
- 使用@Async注解和TaskExecutor来实现。
即:用于处理耗时较长的后台任务,提高系统的响应性和并发处理能力。
(2)、异步请求处理
- 用于处理HTTP请求的异步响应。
- 通常由Web框架(如Spring MVC,springboot)自动支持。
- 使用CompletableFuture、DeferredResult等异步API来实现。
即:用于处理HTTP请求的异步响应,通常由Web框架自动支持。
6、如何使用异步任务
(1)、@EnableAsync
@EnableAsync注解只需要在一个配置类上启用一次,即可在整个应用程序中启用异步任务的支持。即:开启后会告诉Spring框架扫描并管理带有@Async注解的方法。
代码示例:
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
@Configuration
@EnableAsync
public class AsyncConfig {
// 配置类可以包含其他Bean和配置
}
(2)、@Async
1、@Async注解用于标记那些需要异步执行的方法上。注意,方法必须在一个Spring管理的Bean中,否则不会扫描到。
2、@Async标记的方法将在异步任务线程中执行,不会阻塞当前线程,而是立即返回一个Future
对象或CompletableFuture
对象。(这个Future对象现在是无法获取结果的,只能通过定义回调的方式用于异步任务完成后再去通知调用的线程,当然如果无需等待结果的也可以直接忽略返回结果)。
(3)、异步线程池
默认情况下,Spring是使用一个简单的线程池来执行异步任务。你也可以自定义线程池注入spring容器中,以更好地控制异步任务的执行。
3.1、自定义线程池示例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
@EnableAsync // 开启异步任务
public class AsyncConfig {
@Bean // 注入线程池
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
3.2、控制器示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AsyncController {
@Autowired
private AsyncService asyncService;
@GetMapping("/send-email")
public String sendEmail(@RequestParam String recipient, @RequestParam String message) {
// 调用异步方法
CompletableFuture<String> result = asyncService.sendEmail(recipient, message);
// 处理异步结果
result.thenAccept(response -> { // 定义的回调方法,不会阻塞主线程,主线程会定义完成之后直接跳过该方法,往后执行。在异步任务完成后,真正执行回调方法代码的是线程池中的线程。
System.out.println("Response: " + response); // 异步任务完成后,由线程池中的线程执行,主线程不会执行。
});
return "Request accepted, email will be sent asynchronously";
}
}
3.3、异步方法
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class AsyncService {
@Async
public CompletableFuture<String> sendEmail(String recipient, String message) {
// 模拟耗时操作
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 发送邮件
System.out.println("Email sent to " + recipient + ": " + message);
return CompletableFuture.completedFuture("Email sent successfully");
}
}
解释一下:
1、异步方法的调用:
- 在
AsyncController
的sendEmail
方法中,调用asyncService.sendEmail
方法。 sendEmail
方法被标记为@Async
,因此它会在一个异步任务线程中执行。- 方法调用会立即返回一个
CompletableFuture<String>
对象,标识异步任务的结果对象。(这个对象现在是无法获取执行结果的,因为还没有执行完成,需要想要调用线程得到响应,可以通过定义回调的方式在异步任务完成后再通知调用的线程)。
2、处理异步结果: - 使用
result.thenAccept(response -> { ... })
方法来处理异步任务的结果。 thenAccept
方法注册一个回调函数,当异步任务完成时,回调函数会被调用,并传入任务的结果。- 这个回调函数在异步任务线程中执行,不会阻塞主线程。
3、立即返回响应: AsyncController
的sendEmail
方法在调用异步方法后立即返回一个字符串"Request accepted, email will be sent asynchronously"
。- 这个响应告诉客户端请求已经被接受,邮件将在后台异步发送。
学海无涯苦作舟!!!