
本文深入探讨了Java多线程编程中,当使用`ExecutorService`提交任务时,因不当实现`Thread`子类而导致的任务重复执行和命名混乱问题。通过分析错误示例,明确了在`run()`方法中不应创建新的`Thread`实例的陷阱,并提供了基于`Runnable`接口的规范解决方案,强调了`Thread.currentThread()`的正确用法,以确保任务在线程池中高效、准确地执行。
在Java并发编程中,ExecutorService是管理和执行异步任务的核心工具。它通过线程池机制,有效地减少了线程创建和销毁的开销,提高了程序的响应性和吞吐量。然而,不正确的任务提交方式,尤其是当自定义任务类继承Thread并包含不当逻辑时,可能导致意想不到的行为,例如任务重复执行或线程命名混乱。
考虑以下一个尝试使用ExecutorService来模拟线程休眠和唤醒的场景。
原始问题代码示例:
立即学习“Java免费学习笔记(深入)”;
sampleThread.java
import java.util.Random;
public class sampleThread extends Thread {
sampleThread thread; // 成员变量
Random rand = new Random();
public void run() {
thread = new sampleThread(); // 错误:在run()方法中创建新的sampleThread实例
int randSleep = rand.nextInt(1000);
// 使用新创建的'thread'实例的名称
System.out.println(thread.getName() + " is sleeping for " + randSleep + " milliseconds");
try {
Thread.sleep(randSleep);
// 使用新创建的'thread'实例的名称
System.out.println(thread.getName() + " is NOW AWAKE");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}driver.java
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class driver {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Future<?>> futArray = new ArrayList<>();
ExecutorService es = Executors.newFixedThreadPool(6); // 创建一个固定大小的线程池
sampleThread temp = new sampleThread(); // 创建一个sampleThread实例
for (int i = 0; i < 120; i++) {
Future<?> future = es.submit(temp); // 将同一个temp实例提交120次
futArray.add(future);
}
// ... 后续处理,如等待所有任务完成
es.shutdown(); // 关闭线程池
}
}观察到的输出问题: 在上述代码中,程序执行后,可能会观察到最后一个线程的“NOW AWAKE”消息被重复打印多次,例如:
... Thread-119 is NOW AWAKE Thread-120 is sleeping for 487 milliseconds Thread-120 is NOW AWAKE Thread-120 is NOW AWAKE Thread-120 is NOW AWAKE Thread-120 is NOW AWAKE Thread-120 is NOW AWAKE Thread-120 is NOW AWAKE
这与预期中每个任务只打印一次“NOW AWAKE”的情况不符。
问题的核心在于sampleThread类的run()方法中的这一行:thread = new sampleThread();。
这种设计模式是错误的,因为ExecutorService的目的是让你提供一个“任务”(通常是Runnable或Callable的实现),然后由线程池中的线程来执行这个任务。任务本身不应该在执行时再创建并管理自己的线程。
在Java中,当需要定义一个由ExecutorService执行的任务时,最佳实践是实现Runnable或Callable接口,而不是继承Thread类。Runnable接口只定义了一个run()方法,用于封装任务的执行逻辑,而不需要关心线程的创建和管理。
修正后的代码示例:
sampleTask.java (更名为sampleTask以区分Thread类)
import java.util.Random;
// 推荐:实现Runnable接口,而不是继承Thread
public class sampleTask implements Runnable {
private Random rand = new Random(); // 声明为私有成员变量
@Override // 明确重写Runnable接口的run方法
public void run() {
// 获取当前执行此任务的线程的名称,即线程池中的工作线程
String currentThreadName = Thread.currentThread().getName();
int randSleep = rand.nextInt(1000);
System.out.println(currentThreadName + " is sleeping for " + randSleep + " milliseconds");
try {
Thread.sleep(randSleep);
System.out.println(currentThreadName + " is NOW AWAKE");
} catch (InterruptedException e) {
// 优雅地处理中断异常,而不是简单地抛出运行时异常
System.err.println(currentThreadName + " was interrupted while sleeping.");
// 重新设置中断标志,以便更高层级的代码可以检测到中断
Thread.currentThread().interrupt();
}
}
}driver.java
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class driver {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Future<?>> futArray = new ArrayList<>();
ExecutorService es = Executors.newFixedThreadPool(6); // 创建一个固定大小的线程池
for (int i = 0; i < 120; i++) {
// 每次循环创建一个新的sampleTask实例,确保每个任务是独立的
sampleTask task = new sampleTask();
Future<?> future = es.submit(task); // 提交任务
futArray.add(future);
}
// 等待所有任务完成
for (Future<?> future : futArray) {
future.get(); // 阻塞直到任务完成,并获取结果(如果Callable有结果)
}
es.shutdown(); // 关闭线程池
System.out.println("All tasks completed and ExecutorService shut down.");
}
}修正后的输出示例:
pool-1-thread-1 is sleeping for 526 milliseconds pool-1-thread-6 is sleeping for 497 milliseconds pool-1-thread-4 is sleeping for 565 milliseconds pool-1-thread-5 is sleeping for 978 milliseconds pool-1-thread-2 is sleeping for 917 milliseconds pool-1-thread-3 is sleeping for 641 milliseconds pool-1-thread-6 is NOW AWAKE pool-1-thread-6 is sleeping for 847 milliseconds pool-1-thread-1 is NOW AWAKE pool-1-thread-1 is sleeping for 125 milliseconds ... pool-1-thread-3 is NOW AWAKE All tasks completed and ExecutorService shut down.
可以看到,现在输出的线程名称是线程池分配的名称(如pool-1-thread-X),并且每个任务的“NOW AWAKE”只打印一次,行为符合预期。
实现Runnable或Callable而非继承Thread:
正确获取当前执行线程的名称:
每次提交创建新的任务实例:
异常处理:
正确使用Java的ExecutorService是编写高效、健壮并发程序的关键。核心原则是区分“任务”和“线程”:ExecutorService管理线程,我们提供任务。通过实现Runnable或Callable接口来定义任务,并利用Thread.currentThread()获取当前执行线程的信息,可以避免常见的陷阱,确保任务在线程池中以预期的方式执行。
以上就是Java多线程中ExecutorService与任务提交的正确实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号