一、ExecutorService
ExecutorService 接口提供了一种灵活的方式来管理和执行线程。它不仅可以帮助管理线程池
,还可以控制线程的生命周期
,从而提高应用程序的性能和可维护性。
ExecutorService 的作用
- 线程池管理:ExecutorService 提供了一个线程池,可以在其中执行多个任务。这些任务可以是 Runnable 或 Callable 对象。
- 异步任务执行:ExecutorService 可以异步地执行任务,并返回 Future 对象,用于获取任务的结果或取消任务。
- 任务调度:除了执行任务外,某些实现还支持定时执行任务。
- 线程资源管理:自动管理线程的创建、销毁和重用,减少了线程创建和销毁的开销。
ExecutorService 的常见方法
- submit(Runnable task):
提交一个 Runnable 任务用于执行,并返回一个 Future 对象。 - submit(Callable task):
提交一个 Callable 任务用于执行,并返回一个 Future 对象。 - invokeAll(Collection<? extends Callable> tasks):
提交一组 Callable 任务,等待所有任务完成,并返回一个 List<Future>。 - invokeAny(Collection<? extends Callable> tasks):
提交一组 Callable 任务,并返回第一个完成的任务的结果。如果所有任务都失败,则抛出异常。 - shutdown():
关闭 ExecutorService,不再接受新任务,但允许已提交的任务继续执行。 - shutdownNow():
尝试停止所有正在执行的任务,并返回未执行的任务列表。 - isShutdown() 和 isTerminated():
分别用于检查服务是否已经关闭或所有任务是否已经终止。
创建使用 ExecutorService
Java 提供了几种创建线程池的方法:
- Executors.newFixedThreadPool(int nThreads):
创建一个固定大小的线程池。 - Executors.newCachedThreadPool():
创建一个可以根据需要创建新线程的线程池,但会在一段时间内缓存线程以供重用。 - Executors.newScheduledThreadPool(int corePoolSize):
创建一个支持定时及周期性任务执行的线程池。 - Executors.newSingleThreadExecutor():
创建一个只包含一个线程的线程池。
示例:创建一个Runnable
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交 Runnable 任务
executor.submit(() -> {
// 任务执行
System.out.println("Executing Runnable task...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Over...");
});
// 关闭 ExecutorService
executor.shutdown();
}
}
import java.util.concurrent.*;
public class ExecutorServiceRunnableExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交多个 Runnable 任务
for (int i = 0; i < 5; i++) {
int taskId = i;
executor.submit(new Task(taskId));
}
// 关闭 ExecutorService
executor.shutdown();
// 等待所有任务完成
while (!executor.isTerminated()) {
Thread.sleep(100); // 短暂休眠,避免忙循环
}
System.out.println("所有任务已完成!");
}
static class Task implements Runnable {
private final int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("任务 " + taskId + " 正在执行...");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("任务 " + taskId + " 被中断!");
}
System.out.println("任务 " + taskId + " 完成!");
}
}
}
输出:
任务 2 正在执行...
任务 3 正在执行...
任务 0 正在执行...
任务 1 正在执行...
任务 2 完成!
任务 0 完成!
任务 3 完成!
任务 4 正在执行...
任务 1 完成!
任务 4 完成!
所有任务已完成!
二、Future
Future 的主要用途
- 异步结果获取:Future 允许你在计算完成之后获取结果,而不是阻塞主线程等待结果。
- 任务取消:通过 Future 对象,你可以在任何时候取消一个正在执行的任务。
- 任务状态检查:Future 提供了一些方法来检查任务的状态,比如任务是否已经完成。
Future 的常用方法
结果获取
- get():阻塞等待直到计算完成,并返回计算的结果。如果没有设置超时,get() 方法会一直阻塞直到结果可用。
- get(long timeout, TimeUnit unit):阻塞等待直到计算完成或超时,返回计算的结果。如果在指定时间内计算没有完成,则抛出 TimeoutException。
任务取消
- cancel(boolean mayInterruptIfRunning):尝试取消计算。如果计算正在进行中,mayInterruptIfRunning 参数指定了是否中断正在执行的线程。
状态检查
- isCancelled():返回计算是否被取消。
- isDone():返回计算是否完成,无论成功还是失败。、
三、Callable
Callable 接口定义了一个 call() 方法,该方法可以抛出异常。Callable 接口定义了一个 call 方法,该方法可以抛出异常。
Callable
接口和Runnable
相似,但相比于 Runnable 接口提供了几个重要的增强功能,使其更适合于执行需要返回结果的任务和处理可能抛出的异常。
1. 异常处理
- Runnable:Runnable 的 run 方法
不能
抛出受检查的异常(checked exception),只能抛出运行时异常(runtime exception)或错误(error)。 - Callable:Callable 的 call 方法可以抛出受检查的异常。这意味着你可以更好地控制异常的处理逻辑,并且可以在调用者处捕获和处理这些异常。
2. 异步结果获取
- Runnable:当你使用 Runnable 执行任务时,你无法直接获取任务的结果,因为 run 方法
没有
返回值。你需要另外的机制来同步或异步地获取结果。 - Callable:通过 ExecutorService 提交 Callable 任务后,你会得到一个 Future 对象。Future 允许你以同步或异步的方式获取结果。你可以使用 get() 方法阻塞等待结果,也可以使用 isDone()、cancel() 等方法来检查任务状态或取消任务。
示例:在ExecutorService线程池中创建提交Callable线程任务并获取线程返回的结果
import java.util.concurrent.*;
public class Example {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
// 创建并提交 Callable 任务
Future<Integer> future = executor.submit(new SumTask(1, 100));
// 获取结果
Integer result = future.get(); // 阻塞直到任务完成
System.out.println("The sum is: " + result);
// 关闭 ExecutorService
executor.shutdown();
}
static class SumTask implements Callable<Integer> {
private int start;
private int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Integer call() {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
return sum;
}
}
}
使用ExecutorService 运行Runnable和Callable线程:
import java.util.concurrent.*;
public class ThreadPoolTest {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(50); //设置线程池中线程数的上限
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
try {
Future future = service.submit(new NumberThread2());//适合使用于Callable
System.out.println("总和为:" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
//3.关闭连接池
service.shutdown();
}
}
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread2 implements Callable {
@Override
public Object call() throws Exception {
int evenSum = 0;//记录偶数的和
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
evenSum += i;
}
}
return evenSum;
}
}
总结
Callable 本身并不能直接运行,它需要通过某种方式提交给执行器(如 ExecutorService)或包装成 FutureTask 然后在一个线程中执行。这是因为 Callable 接口本身只是一个定义了 call 方法的接口,它并不包含任何执行机制。