• Java线程池并发执行多个任务


    Java在语言层面提供了多线程的支持,线程池能够避免频繁的线程创建和销毁的开销,因此很多时候在项目当中我们是使用的线程池去完成多线程的任务。
    Java提供了Executors 框架提供了一些基础的组件能够轻松的完成多线程异步的操作,Executors提供了一系列的静态工厂方法能够获取不同的ExecutorService实现,ExecutorService扩展了Executors接口,Executors相当简单:

    public interface Executor {
        void execute(Runnable command);
    }
    
    
    • 1
    • 2
    • 3
    • 4

    把任务本身和任务的执行解耦了,如果说Runnable是可异步执行任务的抽象,那Executor就是如何执行可异步执行任务的抽象,说起来比较绕口。
    本文不讲解线程的一些基础知识,因为网上的其他文章已经写的足够详细和泛滥。我写写多个异步任务的并发执行与结果的获取问题。假设这样一个场景:我们要组装一个对象,这个对象由大量小的内容组成,这些内容是无关联无依赖关系的,如果我们串行的去执行,如果每个任务耗时10秒钟,一共有10个任务,那我们就需要100秒才能获取到结果。显然我们可以采用线程池,每个任务起一个线程,忽略线程启动时间,我们只需要10秒钟就能获取到结果。这里还有两种选择,这10秒钟我们可以去做其他事,也可以等待结果。
    我们来完成这样的操作:

    // 这是任务的抽象
    class GetContentTask implements Callable<String> {
    		
    		private String name;
    		
    		private Integer sleepTimes;
    		
    		public GetContentTask(String name, Integer sleepTimes) {
    			this.name = name;
    			this.sleepTimes = sleepTimes;
    		}
    		public String call() throws Exception {
    			// 假设这是一个比较耗时的操作
    			Thread.sleep(sleepTimes * 1000);
    			return "this is content : hello " + this.name;
    		}
    		
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    采用completionService :

    // 方法一
    		ExecutorService executorService = Executors.newCachedThreadPool();
    		CompletionService<String> completionService = new ExecutorCompletionService(executorService);
    		ExecuteServiceDemo executeServiceDemo = new ExecuteServiceDemo();
    		// 十个
    		long startTime = System.currentTimeMillis();
    		int count = 0;
    		for (int i = 0;i < 10;i ++) {
    			count ++;
    			GetContentTask getContentTask = new ExecuteServiceDemo.GetContentTask("micro" + i, 10);
    			completionService.submit(getContentTask);
    		}
    		System.out.println("提交完任务,主线程空闲了, 可以去做一些事情。");
    		// 假装做了8秒种其他事情
    		try {
    			Thread.sleep(8000);
    			System.out.println("主线程做完了,等待结果");
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		try {
    			// 做完事情要结果
    			for (int i = 0;i < count;i ++) {
    				Future<String> result = completionService.take();
    				System.out.println(result.get());
    			}
    			long endTime = System.currentTimeMillis();
    			System.out.println("耗时 : " + (endTime - startTime) / 1000);
    		}  catch (Exception ex) {
    			System.out.println(ex.getMessage());
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    执行结果为:

    提交完任务,主线程空闲了, 可以去做一些事情。
    主线程做完了,等待结果
    this is content : hello micro9
    this is content : hello micro7
    this is content : hello micro2
    this is content : hello micro5
    this is content : hello micro4
    this is content : hello micro8
    this is content : hello micro1
    this is content : hello micro3
    this is content : hello micro0
    this is content : hello micro6
    耗时 : 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    如果多个不想一个一个提交,可以采用 invokeAll一并提交,但是会同步等待这些任务

    // 方法二
    		ExecutorService executeService = Executors.newCachedThreadPool();
    		List<GetContentTask> taskList = new ArrayList<GetContentTask>();
    		long startTime = System.currentTimeMillis();
    		for (int i = 0;i < 10;i ++) {
    			taskList.add(new GetContentTask("micro" + i, 10));
    		}
    		try {
    			System.out.println("主线程发起异步任务请求");
    			List<Future<String>> resultList = executeService.invokeAll(taskList);
    			// 这里会阻塞等待resultList获取到所有异步执行的结果才会执行 
    			for (Future<String> future : resultList) {
    				System.out.println(future.get());
    			}
    			// 主线程假装很忙执行8秒钟
    			Thread.sleep(8);
    			long endTime = System.currentTimeMillis();
    			System.out.println("耗时 : " + (endTime - startTime) / 1000);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    主线程发起异步任务请求
    this is content : hello micro0
    this is content : hello micro1
    this is content : hello micro2
    this is content : hello micro3
    this is content : hello micro4
    this is content : hello micro5
    this is content : hello micro6
    this is content : hello micro7
    this is content : hello micro8
    this is content : hello micro9
    耗时 : 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果一系列请求,我们并不需要等待每个请求,我们可以invokeAny,只要某一个请求返回即可。

    ExecutorService executorService = Executors.newCachedThreadPool();
    		ArrayList<GetContentTask> taskList = new ArrayList<GetContentTask>();
    		taskList.add(new GetContentTask("micro1",3));
    		taskList.add(new GetContentTask("micro2", 6));
    		try {
    			List<Future<String>> resultList = executorService.invokeAll(taskList);// 等待6秒 
    //			String result2 = executorService.invokeAny(taskList); // 等待3秒
    			// invokeAll 提交一堆任务并行处理并拿到结果
    			// invokeAny就是提交一堆并行任务拿到一个结果即可
    			for (Future<String> result : resultList) {
    				System.out.println(result.get());
    			}
    //			System.out.println(result2);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		System.out.println("主线程等待");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    如果我虽然发送了一堆异步的任务,但是我只等待一定的时间,在规定的时间没有返回我就不要了,例如很多时候的网络请求其他服务器如果要数据,由于网络原因不能一直等待,在规定时间内去拿,拿不到就我使用一个默认值。这样的场景,我们可以使用下面的写法:

    try {
    			ExecutorService executorService = Executors.newCachedThreadPool();
    			List<Callable<String>> taskList = new ArrayList<Callable<String>>();
    			taskList.add(new GetContentTask("micro1", 4));
    			taskList.add(new GetContentTask("micro2", 6));
    			// 等待五秒
    			List<Future<String>> resultList = executorService.invokeAll(taskList, 5, TimeUnit.SECONDS);
    			for (Future<String> future : resultList) {
    				System.out.println(future.get());
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		} 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    this is content : hello micro1
    java.util.concurrent.CancellationException
    	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
    	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    	at com.micro.demo.spring.ExecuteServiceDemo.main(ExecuteServiceDemo.java:105)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    因为只等待5秒,6秒的那个任务自然获取不到,抛出异常,如果将等待时间设置成8秒,就都能获取到。

  • 相关阅读:
    mysql 提权总结
    CTF线下赛AWD套路小结
    url的三个js编码函数escape(),encodeURI(),encodeURIComponent()简介
    url的三个js编码函数escape(),encodeURI(),encodeURIComponent()简介
    使用httpclient必须知道的参数设置及代码写法、存在的风险
    使用httpclient必须知道的参数设置及代码写法、存在的风险
    HttpClient学习笔记
    HttpClient学习笔记
    HttpClient系统日志配置
    HttpClient系统日志配置
  • 原文地址:https://www.cnblogs.com/wangsongbai/p/14128813.html
Copyright © 2020-2023  润新知