• 多线程并发Callable


    前言

    平常我们的线程都是继承Thread或者实现Runnable接口,这两种线程的实现方式,都是没有返回值的。
    Callable接口和Runable最大的区别是,Callable接口可以取到返回值。
    业务场景:
    假设有一个业务场景需要取得4个远程接口的数据,并提供到前段展示。很明显如果使用串式的单线程编程,需要按顺序来请求并获取结果,消耗的时间比较长,此时使用Callable,并发请求,将会节省时间。

    案例

    1. 实现Callable接口
    public class TestCallable implements Callable<String> {
    
    	private int id;
    
    	public TestCallable(int id) {
    		this.id = id;
    	}
    
    	@Override
    	public String call() throws Exception {
    		int t = new Random().nextInt(10_000);
    		TimeUnit.MILLISECONDS.sleep(t);
    		System.out.println("id:" + id + ",本任务执行完成了,耗时: " + t + " 毫秒");
    		return "id:" + id + ",本任务执行花了  " + t + " 毫秒";
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 使用ExecutorService调用tasks,进行并发调用
           public static void main(String[] args) {	
    		ExecutorService mExecutor = Executors.newFixedThreadPool(4);
    		
    		/**
    		 * 需要构建的任务
    		 */
    		List<TestCallable> tasks=new ArrayList<>();
    		tasks.add(new TestCallable(1));
    		tasks.add(new TestCallable(2));
    		tasks.add(new TestCallable(3));
    		tasks.add(new TestCallable(4));
    
    		try {
    			long startTime=System.currentTimeMillis();
    			List<Future<String>> results=mExecutor.invokeAll(tasks);//调用
    			System.out.println("=============================");
    			for(Future<String> f:results){
    				String result=f.get();
    				System.out.println(result);
    				System.out.println(System.currentTimeMillis());
    			}
    			System.out.println("=============================");
    			System.out.println("总耗时:  "+(System.currentTimeMillis()-startTime)+"  毫秒");
    		} 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
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. 运行打印结果
      在这里插入图片描述

    分析结果,

    • 查看TestCallable的输出日志,可以发现TestCallable任务是并发执行的,相互独立运行打印了结果。
    • 查看id的打印输出顺序,可以看出返回结果的顺序与放入的task顺序一致。
    • 观察打印的时间戳,可以看出:获取结果是阻塞的,最后一个任务执行完成后,所有结果才一起返回和输出。

    修改线程池大小有什么影响

    上面的代码,使用到ExecutorService 去执行任务,那么假设把newFixedThreadPool设为为1,会有什么效果?

    ExecutorService mExecutor = Executors.newFixedThreadPool(1);
    
    • 1

    修改后,再次执行代码,可以发现,4个任务的执行又变成了串式执行,4个任务的执行时间之和等于总耗时。这说明了Executors.newFixedThreadPool(n)表示了有几只手来执行任务,设定为1,则表示只有1只手来处理池子里的任务,如果设置为4,则表示有4只手处理任务,每只手都可以对应一个任务。
    在这里插入图片描述

    线程池大小怎么设置

    通过上面的日志,我们发现修改这个池子的大小,决定了有几只手执行任务。我们当然是希望这个手越多越好,但是现在的计算机cpu的并发能力如何,cpu的核心数决定了可以有几只手来处理任务。通常推荐的做法时,设置为逻辑核心的倍数,假设cpu时2核4线程,那么设置的线程池应当是4的倍数。
    推荐设置为4的n倍

    /**
    	 * 固定大小的线程池,此参数很重要,需要是CPU的核心数的倍数,例如CPU的内核核心数是4,n为倍数,则此参数为4n
    	 */
    	ExecutorService mExecutor = Executors.newFixedThreadPool(4);
    
    • 1
    • 2
    • 3
    • 4

    获取cpu的核心数

    Runtime.getRuntime().availableProcessors();
    
    • 1

    则可以设置为

    int cpus=Runtime.getRuntime().availableProcessors();
    ExecutorService mExecutor = Executors.newFixedThreadPool(cpus);
    
    • 1
    • 2
  • 相关阅读:
    Dokcer常用命令
    【教学类-08-01】20221010《门牌号(6层*3间 黑色版)》(大班主题《我们的城市》)
    【SpringCloud微服务】- Eureka服务注册与服务发现Discovery
    微软官方操作系统(需空U盘)
    Codeforces Round #827 (Div. 4)
    贪心算法-金条切割问题
    【c++】——类和对象(上)——万字详细解疑
    Spring Aop原理解析和使用示例
    java创建本地文件并读取文件
    数据结构之顶层父类的创建
  • 原文地址:https://blog.csdn.net/u011628753/article/details/126058233