
本文深入探讨了在Java并发编程中使用`ExecutorService`时,由于不当继承`Thread`类并在`run()`方法中重复创建`Thread`实例而导致的常见问题,即任务执行结果混乱和线程名称识别错误。文章通过分析错误代码,阐明了应使用`Runnable`接口将任务逻辑与线程管理解耦,并利用`Thread.currentThread().getName()`准确获取当前执行线程名称的最佳实践,以构建健壮高效的并发应用。
在Java并发编程中,ExecutorService是管理和执行异步任务的强大工具。然而,如果不正确地使用它,可能会导致意想不到的行为,例如任务结果重复或线程身份混淆。本文将分析一个典型的案例,并提供使用Runnable接口和ExecutorService的最佳实践。
在某些情况下,开发者可能会遇到在使用ExecutorService提交任务时,最后一个任务的完成信息被重复打印多次的现象。这通常发生在自定义的任务类继承了Thread,并且在run()方法内部错误地创建了新的Thread实例。
考虑以下一个简化的示例代码结构:
立即学习“Java免费学习笔记(深入)”;
错误的sampleThread.java实现:
import java.util.Random;
public class sampleThread extends Thread { // 错误:直接继承Thread
sampleThread thread; // 错误:在任务类中声明一个自身的实例
Random rand = new Random();
public void run() {
thread = new sampleThread(); // 错误:在run方法内部创建新的Thread实例
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) {
Thread.currentThread().interrupt(); // 更好的中断处理
throw new RuntimeException(e);
}
}
}driver.java提交任务:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
// import java.util.concurrent.ExecutionException; // 如果不调用future.get(),可以不导入
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++) {
// 将同一个temp实例提交给线程池
Future<?> future = es.submit(temp);
futArray.add(future);
}
es.shutdown(); // 关闭线程池,等待所有任务完成
// 可以选择等待所有任务完成
// for (Future<?> future : futArray) {
// try {
// future.get();
// } catch (InterruptedException | ExecutionException e) {
// e.printStackTrace();
// }
// }
}
}当上述代码运行时,可能会观察到类似以下输出:
Thread-117 is sleeping for 547 milliseconds Thread-117 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”信息被重复打印了多次。
这个问题的核心在于对Java并发编程模型和ExecutorService工作原理的误解。
不当继承Thread并内部创建实例:
ExecutorService与Runnable的关系:
解决这个问题的正确方法是遵循Java并发编程的最佳实践:将任务逻辑封装在Runnable接口中,并使用Thread.currentThread()来获取当前执行任务的线程信息。
实现Runnable接口:
使用Thread.currentThread().getName():
修正后的sampleThread.java实现:
import java.util.Random;
// 正确:实现Runnable接口,将任务逻辑与线程管理分离
public class sampleThread implements Runnable {
Random rand = new Random();
@Override // 明确重写Runnable接口的run方法
public void run() {
int randSleep = rand.nextInt(1000);
// 正确:获取当前执行任务的工作线程的名称
System.out.println(Thread.currentThread().getName() + " is sleeping for " + randSleep + " milliseconds");
try {
Thread.sleep(randSleep);
// 正确:获取当前执行任务的工作线程的名称
System.out.println(Thread.currentThread().getName() + " is NOW AWAKE");
} catch (InterruptedException e) {
// 当线程被中断时,设置中断标志并处理
Thread.currentThread().interrupt();
throw new RuntimeException("Thread interrupted during sleep", e);
}
}
}修正后的driver.java提交任务:
driver.java中的主要改动是提交任务的方式,现在每次循环都创建一个新的sampleThread实例(一个Runnable任务)并提交。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
// import java.util.concurrent.ExecutionException; // 如果不调用future.get(),可以不导入
public class driver {
public static void main(String[] args) {
List<Future<?>> futArray = new ArrayList<>();
ExecutorService es = Executors.newFixedThreadPool(6); // 创建一个固定大小的线程池
for (int i = 0; i < 120; i++) {
// 正确:每次提交一个独立的Runnable任务实例
Future<?> future = es.submit(new sampleThread());
futArray.add(future);
}
es.shutdown(); // 关闭线程池,等待所有任务完成
// 可以选择等待所有任务完成
// for (Future<?> future : futArray) {
// try {
// future.get();
// } catch (InterruptedException | ExecutionException e) {
// e.printStackTrace();
// }
// }
}
}使用上述修正后的代码,输出将是清晰且符合预期的,每个任务都会由线程池中的一个工作线程执行,并正确打印其状态:
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-X是ExecutorService内部管理的工作线程的名称,输出清晰地展示了6个线程在并发执行120个任务。
Runnable vs Thread:
ExecutorService的作用:
避免在任务内部创建新线程:
正确获取当前线程信息:
在Java并发编程中,理解ExecutorService与Runnable、Thread之间的关系至关重要。当使用ExecutorService时,应将任务逻辑封装在实现Runnable接口的类中,并避免在run()方法内部创建新的Thread实例。同时,使用Thread.currentThread().getName()可以确保获取到执行任务的实际工作线程的正确名称。遵循这些最佳实践,可以帮助我们构建出更加健壮、高效且易于调试的并发应用程序。
以上就是Java并发编程:ExecutorService与Runnable的正确实践的详细内容,更多请关注php中文网其它相关文章!
编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号