1.写在前面
Executors 是一个工具类,提供了创建不同类型的 ExecutorService 和 ScheduledExecutorService 的静态工厂方法。在日常工作中涉及到并发编程基本都会用到这个,但是你真正了解它吗?下面这几个问题你是否有思考过?
- submit() 和 execute() 方法有什么区别?
- 如何取消一个正在执行的任务?
- 如何优雅地关闭线程池?
- 如何获取线程池的状态?
- Callable 和 Runnable 有什么区别?
2. 从使用说起
2.1 创建固定大小的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
2.2 创建单线程的线程池
适用于需要保证任务按顺序执行的场景。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
2.3 创建可缓存的线程池
适用于执行大量短时间任务的场景,线程池会根据需要动态调整线程数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
2.4 创建调度线程池
适用于需要延迟执行或周期性执行任务的场景。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 延迟 2 秒后执行任务
scheduledExecutorService.schedule(() -> {
System.out.println("Task executed after 2 seconds delay.");
}, 2, TimeUnit.SECONDS);
// 每 3 秒执行一次任务
scheduledExecutorService.scheduleAtFixedRate(() -> {
System.out.println("Task executed at fixed rate of 3 seconds.");
}, 1, 3, TimeUnit.SECONDS);
// 每 4 秒执行一次任务,前一次任务结束后再等待 4 秒
scheduledExecutorService.scheduleWithFixedDelay(() -> {
System.out.println("Task executed with fixed delay of 4 seconds.");
}, 1, 4, TimeUnit.SECONDS);
// 为了演示方便,延迟 20 秒后关闭调度线程池
scheduledExecutorService.schedule(() -> {
scheduledExecutorService.shutdown();
}, 20, TimeUnit.SECONDS);
}
}
2.5 提交 Callable 任务并获取结果
适用于需要任务返回结果的场景。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Callable<String> task = () -> {
Thread.sleep(2000);
return "Task result";
};
Future<String> future = executorService.submit(task);
try {
// 做一些其他事情
System.out.println("Doing other tasks...");
// 获取任务结果
String result = future.get();
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
2.6 使用 invokeAll 提交一组任务并等待所有任务完成
适用于需要同时提交多个任务并等待它们全部完成的场景。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class InvokeAllExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 5; i++) {
tasks.add(() -> {
Thread.sleep(1000);
return Thread.currentThread().getName() + " completed task.";
});
}
try {
List<Future<String>> futures = executorService.invokeAll(tasks);
for (Future<String> future : futures) {
System.out.println(future.get());
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
2.7 使用 invokeAny 提交一组任务并返回第一个成功完成的任务结果
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class InvokeAnyExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 5; i++) {
tasks.add(() -> {
Thread.sleep(1000);
return Thread.currentThread().getName() + " completed task.";
});
}
try {
String result = executorService.invokeAny(tasks);
System.out.println("First completed task result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
}
3. submit() 和 execute() 方法有什么区别?
submit() 和 execute() 是 ExecutorService 提供的两个主要方法,用于提交任务到线程池中执行。虽然它们都用于执行任务,但它们在功能和使用场景上有所不同。
3.1 execute() 方法
- execute() 方法是 Executor 接口中定义的方法,用于提交一个不返回结果的 Runnable 任务。
- 它不会返回任何结果,只是简单地执行任务。
- 如果任务在执行过程中抛出异常,异常会直接抛出到调用线程中。
3.2 submit() 方法
- submit() 方法是 ExecutorService 接口中定义的方法,它可以提交 Runnable 或 Callable 任务。
- 它返回一个 Future 对象,通过这个 Future 对象可以获取任务的执行结果或状态。
- submit(Runnable task) 方法返回的 Future 对象的 get() 方法将返回 null,因为 Runnable 任务不返回结果。
- submit(Callable task) 方法返回的 Future 对象的 get() 方法将返回 Callable 任务的执行结果。
- 如果任务在执行过程中抛出异常,异常会被封装在 Future 对象中,可以通过 Future.get() 方法抛出。
3.3 示例代码
以下是一个示例代码,展示了 execute() 和 submit() 方法的区别。
import java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 使用 execute() 方法提交任务
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task using execute()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟异常
if (true) {
throw new RuntimeException("Exception in execute()");
}
});
// 使用 submit(Runnable) 方法提交任务
Future<?> future1 = executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task using submit(Runnable)");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 模拟异常
if (true) {
throw new RuntimeException("Exception in submit(Runnable)");
}
});
// 使用 submit(Callable) 方法提交任务
Future<String> future2 = executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + " is executing task using submit(Callable)");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Task result from submit(Callable)";
});
// 获取 submit(Runnable) 的结果
try {
future1.get(); // 这里会抛出异常,因为任务中抛出了 RuntimeException
} catch (InterruptedException | ExecutionException e) {
System.out.println("Exception caught from submit(Runnable): " + e.getCause());
}
// 获取 submit(Callable) 的结果
try {
String result = future2.get();
System.out.println("Result from submit(Callable): " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}
运行结果
pool-1-thread-1 is executing task using execute()
pool-1-thread-2 is executing task using submit(Runnable)
Exception caught from submit(Runnable): java.lang.RuntimeException: Exception in submit(Runnable)
pool-1-thread-1 is executing task using submit(Callable)
Result from submit(Callable): Task result from submit(Callable)
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: Exception in execute()
at ExecutorExample.lambda$main\$0(ExecutorExample.java:12)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
4. 如何取消一个正在执行的任务?
可以通过 Future 对象的 cancel 方法来取消一个正在执行的任务。cancel 方法有一个布尔类型的参数 mayInterruptIfRunning,用于指定是否中断正在执行的任务。
- mayInterruptIfRunning 为 true:如果任务正在运行,则会尝试中断任务。
- mayInterruptIfRunning 为 false:即使任务正在运行,也不会中断任务,只会取消尚未开始的任务。
以下是一个示例代码,演示如何取消一个正在执行的任务:
import java.util.concurrent.*;
public class CancelTaskExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 提交一个长时间运行的任务
Future<?> future = executorService.submit(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " is executing task... " + i);
Thread.sleep(1000); // 模拟长时间运行的任务
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
});
// 等待 3 秒后尝试取消任务
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Attempting to cancel the task...");
boolean isCancelled = future.cancel(true); // 尝试取消任务,传入 true 表示允许中断正在执行的任务
if (isCancelled) {
System.out.println("Task was cancelled.");
} else {
System.out.println("Task could not be cancelled.");
}
executorService.shutdown();
}
}
运行结果:
pool-1-thread-1 is executing task... 0
pool-1-thread-1 is executing task... 1
pool-1-thread-1 is executing task... 2
Attempting to cancel the task...
pool-1-thread-1 was interrupted.
Task was cancelled.
4.1 代码解析
- 创建线程池:使用 Executors.newFixedThreadPool 创建一个固定大小的线程池。
- 提交任务:使用 submit 方法提交一个长时间运行的任务,该任务会每秒打印一次信息,共打印 10 次。
- 等待 3 秒:在主线程中等待 3 秒,以便任务有时间开始执行。
- 取消任务:调用 future.cancel(true) 尝试取消任务,传入 true 表示允许中断正在执行的任务。
- 检查取消结果:根据 cancel 方法的返回值判断任务是否成功取消,并打印相应信息。
- 关闭线程池:调用 shutdown 方法关闭线程池。
4.2 注意事项
- 任务是否能够被中断取决于任务的实现。任务需要在适当的位置检查中断状态,并在检测到中断时及时响应。例如,在长时间运行的任务中,可以使用 Thread.sleep、Thread.interrupted 或其他检查中断状态的方法来检测中断。
- 如果任务没有适当处理中断请求,即使调用 cancel(true) 也无法中断任务。
5. 如何使用 ScheduledExecutorService 实现周期性任务调度?
ScheduledExecutorService 是 Java 中用于执行定时任务和周期性任务的接口。它提供了多种方法来调度任务,包括延迟执行、固定速率执行和固定延迟执行。以下是一些常见的使用场景及其对应的代码示例。
5.1 延迟执行任务
使用 schedule 方法可以在指定的延迟后执行一次任务。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class DelayedTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed after delay");
// 延迟 5 秒后执行任务
scheduler.schedule(task, 5, TimeUnit.SECONDS);
// 为了演示方便,延迟 10 秒后关闭调度线程池
scheduler.schedule(() -> scheduler.shutdown(), 10, TimeUnit.SECONDS);
}
}
5.2 固定速率执行任务
使用 scheduleAtFixedRate 方法可以以固定的速率周期性地执行任务。任务的开始时间间隔是固定的,不受任务执行时间的影响。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FixedRateTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed at fixed rate: " + System.currentTimeMillis());
// 初始延迟 1 秒,然后每 3 秒执行一次任务
scheduler.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
// 为了演示方便,延迟 20 秒后关闭调度线程池
scheduler.schedule(() -> scheduler.shutdown(), 20, TimeUnit.SECONDS);
}
}
5.3 固定延迟执行任务
使用 scheduleWithFixedDelay 方法可以在每次任务执行完成后,等待指定的延迟时间再执行下一次任务。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class FixedDelayTaskExample {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> {
System.out.println("Task executed with fixed delay: " + System.currentTimeMillis());
try {
Thread.sleep(2000); // 模拟任务执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
};
// 初始延迟 1 秒,然后每次任务执行完成后等待 3 秒再执行下一次任务
scheduler.scheduleWithFixedDelay(task, 1, 3, TimeUnit.SECONDS);
// 为了演示方便,延迟 20 秒后关闭调度线程池
scheduler.schedule(() -> scheduler.shutdown(), 20, TimeUnit.SECONDS);
}
}
5.4 注意事项
- scheduleAtFixedRate:适用于需要以固定速率执行任务的场景。任务的开始时间间隔是固定的,不受任务执行时间的影响。如果任务执行时间超过了间隔时间,下一次任务会立即执行。
- scheduleWithFixedDelay:适用于需要在每次任务执行完成后等待一段时间再执行下一次任务的场景。任务的执行间隔时间是固定的,受任务执行时间的影响。
- 任务执行时间较长时,可能会影响下一次任务的执行时间,特别是在使用 scheduleAtFixedRate 方法时需要注意。
系列文章
7.jdk源码阅读之ConcurrentHashMap(上)
8.jdk源码阅读之ConcurrentHashMap(下)
17.jdk源码阅读之LinkedBlockingQueue