固定线程池:线程池中的线程数是固定的,线程池创建时就已经设定了固定的线程数量。在任务提交时,线程池会将任务分配给空闲的线程执行。如果所有线程都在执行任务,新的任务会被放到任务队列中,直到有线程空闲出来。
线程复用:线程池中的线程会被复用,也就是说,线程池中的线程在任务执行完后并不会销毁,而是会被复用来执行其他任务。这能够提高任务的处理效率,避免了频繁创建和销毁线程的开销。
队列管理:如果提交的任务超过了线程池中线程的数量,任务会被放入任务队列中。队列有大小限制,超出限制时会根据拒绝策略来处理(例如抛出异常、丢弃任务等)。
代码示例
import java.util.concurrent.*;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(13); // 创建一个固定大小为 13 的线程池
// 提交多个任务
for (int i = 0; i < 20; i++) {
int taskId = i;
executorService.execute(() -> {
System.out.println("任务 " + taskId + " 开始执行, 线程: " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟任务执行过程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务 " + taskId + " 执行完成");
});
}
executorService.shutdown(); // 关闭线程池
}
}
代码解释:
创建线程池:
Executors.newFixedThreadPool(13)
创建一个固定大小为 13 的线程池。线程池可以同时执行 13 个任务。提交任务:通过
executorService.execute()
方法提交 20 个任务,线程池会把这些任务分配给空闲的线程来执行。如果有超过 13 个任务,其他任务会被放入任务队列中,等待有线程空闲时再执行。关闭线程池:
executorService.shutdown()
会关闭线程池,不再接受新的任务。所有已经提交的任务会继续执行,直到执行完毕。
注意事项:
线程池大小:根据任务的数量和执行的需求,线程池大小应该合理设置。创建过多的线程会导致资源竞争和上下文切换的开销;线程池太小则可能导致任务堆积和延迟执行。
关闭线程池:通过
shutdown()
或shutdownNow()
来关闭线程池,建议在所有任务完成后关闭线程池。如果线程池不再使用,及时关闭能够释放资源。任务队列:线程池会使用一个任务队列来缓存那些等待执行的任务。如果任务队列满了,你可能会遇到任务拒绝的情况。你可以通过设置自定义的拒绝策略来应对这种情况。
扩展内容
定时任务池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
这行代码的作用是创建一个大小为 1 的定时任务线程池。ScheduledExecutorService
是 ExecutorService
的一个子接口,它提供了调度任务的功能,包括定时任务和周期性任务的执行。
关键点:
定时任务:
ScheduledExecutorService
允许你以固定的延迟时间执行任务,或者按照固定的时间间隔周期性地执行任务。线程池大小:在这个例子中,线程池的大小为 1,意味着线程池中只有一个线程可用于执行任务。如果你提交多个定时任务,它们会依次排队执行。只有一个线程的线程池适用于那些对并发要求不高的任务。
常见方法:
schedule()
: 用于在指定延迟后执行一个任务。scheduleAtFixedRate()
: 用于按固定频率执行任务,即每隔一定时间执行一次,适用于周期性任务。scheduleWithFixedDelay()
: 用于在执行完一个任务后延迟一定时间再执行下一个任务,适用于任务间有延迟需求的情况。
代码示例:
import java.util.concurrent.*;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
// 创建一个定时任务线程池,池中只有一个线程
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// 使用 schedule() 方法定时执行任务
scheduledExecutorService.schedule(() -> {
System.out.println("任务执行了,延迟 2 秒");
}, 2, TimeUnit.SECONDS); // 延迟 2 秒执行任务
// 使用 scheduleAtFixedRate() 方法按固定频率执行任务
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("周期性任务执行,每 3 秒执行一次");
}, 0, 3, TimeUnit.SECONDS); // 初始延迟 0 秒,之后每 3 秒执行一次
// 使用 scheduleWithFixedDelay() 方法按固定延迟执行任务
scheduledExecutorService.scheduleWithFixedDelay(() -> {
System.out.println("任务执行完后,延迟 1 秒再执行下一个任务");
}, 0, 1, TimeUnit.SECONDS); // 初始延迟 0 秒,任务之间的间隔 1 秒
// 模拟主线程等待一段时间后关闭线程池
try {
Thread.sleep(10000); // 等待 10 秒,观察任务执行情况
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
scheduledExecutorService.shutdown(); // 关闭线程池
}
}
代码解释:
schedule()
方法:用于指定一个任务在指定延迟后执行。这里我们设置了延迟 2 秒后执行任务。scheduleAtFixedRate()
方法:用于周期性地执行任务,初始延迟为 0 秒,每 3 秒执行一次。scheduleWithFixedDelay()
方法:用于按固定的延迟时间执行任务,任务执行完成后会延迟 1 秒再执行下一个任务。shutdown()
:在所有任务完成后,调用shutdown()
方法来关闭线程池,避免资源泄露。
注意事项:
线程池的大小:由于线程池的大小为 1,所有提交的任务将依次排队执行。如果有多个周期性任务,它们将按顺序执行,无法并行。如果需要并行执行多个任务,可以增加线程池的大小。
任务提交方式:你可以根据需要选择使用
schedule()
,scheduleAtFixedRate()
, 或scheduleWithFixedDelay()
方法。选择合适的方式取决于任务的特性,比如是否是周期性任务,是否需要固定的时间间隔等。关闭线程池:使用
shutdown()
或shutdownNow()
来关闭线程池。要确保所有任务执行完之后再关闭线程池,避免任务被中断。
执行任务方式
executorService.execute(() -> { ... })
是 Java 中使用线程池执行任务的一种方式,它是 ExecutorService
接口中的方法之一。这个方法接受一个实现了 Runnable
接口的任务,并将其提交给线程池执行。
在这个例子中,execute()
方法会提交一个无返回值的任务(即 Runnable
)给线程池执行。当你使用 Lambda 表达式时,() -> { ... }
是一种简洁的写法,表示创建一个匿名 Runnable
实现类。
ExecutorService executorService = Executors.newFixedThreadPool(2); // 创建一个包含两个线程的线程池
executorService.execute(() -> {
System.out.println("任务开始执行");
// 任务逻辑
try {
Thread.sleep(1000); // 模拟任务执行过程中的等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务执行完成");
});
executorService.shutdown(); // 关闭线程池
关键点:
execute()
方法不会返回值,适用于那些没有返回结果的任务。如果你需要获取任务的执行结果,应该使用submit()
方法,它返回一个Future
对象。Runnable
接口代表一个无返回值的任务,你可以通过 Lambda 表达式或者匿名内部类的方式来实现。ExecutorService
是Executor
接口的子接口,提供了更多的控制方法,如shutdown()
用来关闭线程池。
注意事项:
如果线程池已经被关闭,或者正在终止,调用
execute()
提交任务时会抛出RejectedExecutionException
。
eg:
如果你希望在任务执行后获取结果,使用
submit()
而不是execute()
,因为submit()
返回一个Future
对象,允许你获取执行结果或者捕获异常。