Java线程之ExecutorService.invokeAny()

本文介绍了如何使用Java线程的ExecutorService.invokeAny()方法来实现多线程并行计算。该方法适用于启动多个线程执行独立任务,一旦有一个线程完成任务并获取结果,它会立即停止所有其他线程。文中通过一个趣味性的例子,即搜索硬盘中特定文件的场景,来解释invokeAny()的实用价值。示例代码展示了如何创建任务列表并调用invokeAny()获取首个结果,从而提高程序效率。

有一类多线程编程模式是这样的:启动多个线程,相互独立的(无同步)去计算一个结果,当某一个线程得到结果之后,立刻终止所有线程,因为只需要一个结果就够了。

实际应用场景:作为男生,电脑上必须有苍老师的爱情动作片。这种片子必须藏得非常隐蔽,隐蔽到什么程度呢?自己都忘了把它藏哪里了,这可咋办啊?编写多线程程序,针对每一个硬盘分区,启动一个线程,搜索该硬盘分区上的所有文件,找名字中含有“苍老师”的文件。由于这些片子都是集中存放,因此,我只需要找到一个,就无需在继续寻找。例如,某线程在D盘找到了一个苍老师的文件,则此线程立刻终结,同时,处理C盘,E盘的线程也应该立刻终结,因为既然D盘找到了,其他盘搜索就毫无意义了,肯定没有结果。


ExecutorService.invokeAny()就是为此设计的,他接收的参数是一个List,List中的每一个元素必须实现Callable接口。他的功能是依此启动新的线程执行List中的任务,并将第一个得到的结果作为返回值,然后立刻终结所有的线程。其用法如下:

String s =  es.invokeAny(list)

具体的代码如下:

public class ThreadLocalTest {

	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(10);
		List<Callable<String>> list = new ArrayList<>();
		
		//针对每一个硬盘分区,定义一个任务,查找名字中包含有“苍老师”的文件
		FileSystem fs = FileSystems.getDefault();
		for(Path root : fs.getRootDirectories()) {
			if(Files.isDirectory(root)) {
				list.add(new FileFinder("苍老师", root));
			}
		}
		
		//使用invokeAny()方法,当任意一个线程找到结果之后,立刻终结所有线程
		try {
			System.out.println(es.invokeAny(list));
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
		}
		
		es.shutdown();
	}
	
}


//定义一个文件查找类,实现Callable接口,可作为线程启动
//同时继承自SimpleFileVisitor,目的是成为一个FileVIsitor,
//在Nio 2中新出现的遍历文件夹方法,需要一个FileVIsitor作为访问器
class FileFinder extends SimpleFileVisitor<Path> implements Callable<String>{

	private String key_word;
	private Path path;
	private String result;

	public FileFinder(String key_word, Path path) {
		super();
		this.key_word = key_word;
		this.path = path;
	}

	@Override
	public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
			throws IOException {
		if(file.toString().contains(key_word)) { 
			//若找到了指定的文件,则结束遍历
			result = file.toString(); 
			return FileVisitResult.TERMINATE;
		}else {
			return FileVisitResult.CONTINUE;
		}
	}
	
	@Override
	public FileVisitResult visitFileFailed(Path file, IOException exc)
			throws IOException {
		// 这个方法必须重载,因为遇到系统文件后,可能会被拒绝访问并抛出异常
		// 重载了这个方法后,遇到问题直接跳过,就不会抛出异常了
		return FileVisitResult.SKIP_SUBTREE;
	}

	@Override
	public String call() throws Exception {
		try {
			Files.walkFileTree(path, this);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(result == null) {
			//若没找到,则抛出一个异常
			//该异常的作用就是告诉invokeAny()函数,此线程未能找到合适的结果
			//invokeAny()函数会很好的处理该异常,这是该函数的正常处理机制
			throw new Exception("没找到");
		}
		return result;
	}
	
}


Java 中,`ExecutorService` 提供了多种执行线程任务的方法,其中 `execute()` 是最基本的方法之一。它继承自 `Executor` 接口,并被 `ExecutorService` 接口所继承。`execute()` 方法用于提交一个 `Runnable` 任务,但不返回任何结果,也不支持异常处理。其基本使用方式如下: ```java ExecutorService executor = Executors.newFixedThreadPool(4); executor.execute(() -> { System.out.println("Task is running in a separate thread."); }); ``` 该方法适用于那些不需要返回值且不需要处理异常的任务。`execute()` 方法没有返回值,因此无法判断任务是否完成或获取其结果[^1]。 与 `execute()` 不同的是,`submit()` 方法可以返回一个 `Future` 对象,允许调用者获取任务执行的结果或检查任务的状态。`submit()` 支持 `Runnable` 和 `Callable` 两种任务类型。例如: ```java Future<?> future = executor.submit(() -> { System.out.println("Task submitted with submit() method."); }); ``` 此外,`ExecutorService` 还提供了 `invokeAll()` 和 `invokeAny()` 方法来批量提交任务。`invokeAll()` 会提交一组任务并等待所有任务完成,返回一个包含每个任务结果的 `Future` 列表;而 `invokeAny()` 则会在任意一个任务完成后立即返回该任务的结果[^1]。 在使用完 `ExecutorService` 后,应调用 `shutdown()` 方法来优雅地关闭线程池,确保所有已提交的任务都执行完毕后才释放资源。如果需要立即终止所有任务,可以使用 `shutdownNow()` 方法[^1]。 ### `execute()` 方法的适用场景 - 适用于不需要返回值的任务。 - 适用于任务执行过程中不需要异常处理的场景。 - 适用于任务提交频率较低、不需要复杂任务管理的场景。 ### `execute()` 与 `submit()` 的区别 | 特性 | `execute()` | `submit()` | |------------------|--------------------------------------|----------------------------------------| | 返回值 | 无 | 有(返回 `Future`) | | 异常处理 | 不支持 | 支持 | | 支持的任务类型 | 仅支持 `Runnable` | 支持 `Runnable` 和 `Callable` | | 任务状态查询 | 不支持 | 支持通过 `Future` 查询 | ### 示例代码 以下是一个完整的示例,展示了如何使用 `execute()` 方法: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecuteExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); executor.execute(() -> { System.out.println("First task is running."); }); executor.execute(() -> { System.out.println("Second task is running."); }); executor.shutdown(); } } ``` 在上述代码中,两个任务被提交到一个固定大小为 2 的线程池中,并通过 `execute()` 方法执行。最后调用 `shutdown()` 方法关闭线程池。 ### 总结 `execute()` 方法是 `ExecutorService` 中用于执行任务的基础方法之一,适用于不需要返回值且不需要异常处理的任务。相比之下,`submit()` 方法提供了更丰富的功能,如返回结果和异常处理。根据具体需求选择合适的方法可以提高代码的可维护性和效率。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值