一、传统线程
默认的线程创建不可复用,执行完会销毁,重复创建和销毁会浪费资源及时间
Runnable task1 = () -> System.out.println("run1..." + Thread.currentThread().getId());
Runnable task2 = () -> System.out.println("run2..." + Thread.currentThread().getId());
Runnable task3 = () -> System.out.println("run3..." + Thread.currentThread().getId());
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
Thread thread3 = new Thread(task3);
thread1.start();
thread2.start();
thread3.start();
二、线程池
是一种多线程处理形式,处理过程中将任务添加到队列,然后分发给线程执行。线程池的主要作用包括:
- 重用线程:线程池会在内部维护一组可重用的线程,避免了频繁地创建和销毁线程的开销,提高了线程的利用率。
- 控制并发度:线程池可以限制并发执行的线程数量,防止系统过载。通过调整线程池的大小,可以控制并发度,避免资源消耗过大。
- 提供线程管理和监控:线程池提供了一些管理和监控机制,例如线程池的创建、销毁、线程状态的监控等,方便开发人员进行线程的管理和调试。
- 提供任务队列:线程池通常会使用任务队列来存储待执行的任务,这样可以实现任务的缓冲和调度。
三、线程池的关键组成部分:
线程池管理器(ThreadPoolExecutor):负责创建、管理和控制线程池。它负责线程的创建、销毁和管理,以及线程池的状态监控和调度任务。
工作队列(BlockingQueue):用于存储待执行的任务。当线程池中的线程都在执行任务时,新的任务会被放入工作队列中等待执行。
线程池线程(Worker Thread):实际执行任务的线程。线程池中会维护一组线程,这些线程可以被重复使用,从而避免了频繁创建和销毁线程的开销。
//构造方法
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
)
{ ... }
四、ExecutorService
ExecutorService接口是java内置的线程池接口,通过学习接口中的方法,可以快速的掌握java内置线程池的基本使用
常用方法:
- void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
- List<Runnable> shutdownNow() 停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
- <T> Future<T> submit(Callable<T> task) 执行带返回值的任务,返回一个Future对象。
- Future<?> submit(Runnable task) 执行 Runnable 任务,并返回一个表示该任务的 Future。
- <T> Future<T> submit(Runnable task, T result) 执行 Runnable 任务,并返回一个表示该任务的 Future。
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
ExecutorService executorService = new ThreadPoolExecutor(
3,
5,
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 模拟耗时操作
Thread.sleep(1000);
return 123;
}
});
try {
Integer result = future.get(); // 阻塞等待结果
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown(); // 关闭线程池
}
}
}
ThreadPoolExecutor 提供了四种内置的拒绝策略:
- AbortPolicy:默认策略,直接抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:将任务交给调用者线程(即提交任务的线程)来执行。
- DiscardPolicy:默默地丢弃无法处理的任务。
- DiscardOldestPolicy:丢弃队列中最老的一个任务,然后尝试重新提交当前任务。
五、execute和submit
在Java的ExecutorService中,execute和submit是两个用于提交任务到线程池的方法,但它们之间有一些重要的区别:
1. 返回类型不同
execute(Runnable command):
该方法接受一个Runnable类型的参数。它没有返回值,即返回类型为void。
主要用于那些不需要返回结果的任务。
submit:
submit方法有多个重载版本,可以接受Runnable或Callable类型的参数。
当提交一个Runnable任务时,submit方法会返回一个Future<?>对象,表示任务的未来结果。虽然Runnable本身不返回任何结果,但Future对象可以用来检查任务是否完成或取消任务。
当提交一个Callable任务时,submit方法会返回一个Future<V>对象,其中V是Callable任务的返回类型。通过Future.get()方法可以获取任务的返回值。
2. 使用场景不同
execute:
适用于那些只需要执行某个操作而不需要关心执行结果的场景。
例如,后台日志记录、定时任务等。
submit:
适用于需要获取任务执行结果或处理任务异常的场景。
例如,计算密集型任务、数据处理任务等。
六、多线程模拟实例
public static void main(String[] args) {
int numberOfTasks = 30; // 定义任务数量
ExecutorService executorService = new ThreadPoolExecutor(
3, // 核心线程数
5, // 最大线程数
3, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 工作队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < numberOfTasks; i++) {
final int taskId = i;
Future<Integer> future = executorService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"Task " + taskId + " started");
Thread.sleep(1000 + (taskId * 100)); // 模拟不同耗时的操作
System.out.println(Thread.currentThread().getName()+"Task " + taskId + " completed");
return taskId * 10; // 返回任务ID乘以10作为结果
}
});
futures.add(future);
}
// 收集所有任务的结果
for (Future<Integer> future : futures) {
try {
Integer result = future.get(); // 阻塞等待结果
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executorService.shutdown(); // 关闭线程池
}
1.执行循序
循环 i = 0~29,合计30次循环,其中submit是并发操作,循环最后会依次将future加入到futures集合中,进入到处理结果的循环中,此处都是顺序执行,会按照futures中存储的顺序依次打印结果,如果get不到结果,会阻塞等待。
2.并发顺序:
核心线程数为 3,最大线程数为 5,工作队列容量为 3。
当前 3 个核心线程都在忙时,新的任务会被放入工作队列(加上核心线程合计5个)。
当核心线程和工作队列都满时,新的任务会由调用者线程(即主线程)执行,直至有空闲线程。
七、注册线程池Bean
1.注册线程池单例 Bean
@Configuration
@Data
public class ExecutorConfig {
@Bean(name="taskPool")
public Executor asyncTaskPool(){
//此类由Spring提供,org.springframework.scheduling.concurrent包下,是线程池的封装类
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//线程池中线程的名字前缀
executor.setThreadNamePrefix("async-task-");
//线程池核心线程数量
executor.setCorePoolSize(4);
//线程池最大线程数量
executor.setMaxPoolSize(10);
//线程池空闲线程存活时间,单位秒
executor.setKeepAliveSeconds(100);
//线程池拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程池中核心线程是否允许超时,默认为false
executor.setAllowCoreThreadTimeOut(true);
//线程池中的超时处理时间,单位秒
executor.setAwaitTerminationSeconds(1200);
//初始化线程池,不可以少,否者会抛出 线程池没有初始化
executor.initialize();
return executor;
}
}
2.使用线程池
@Autowired
@Qualifier("taskPool")
private ExecutorService executorService;
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
//......
},executorService);
170万+






