【java并发编程】线程返回值

1.场景

​ 假如有个需求,随机出10个数,打印比x大的数,那我们可以用for循环这10个数,找到比x大的数即可;

​ 那么如果是随机100万个数中打印比x大的数,那这样我们就可以充分利用多线程的优势了。

    public static void main(String[] args) {
        final int x=4000;
        // 10个线程一起跑
        for(int y = 0;y<10;y++){
            new Thread(()->{
                Random random = new Random();
                // 循环10万次
                for(int i =0;i<100000;i++){
                    int num = random.nextInt(1000000);
                    if(num>x){
                        System.out.println(num);
                    }
                }
            }).start();
        }
    }

​ 我们发现10个线程一起跑,每个线程选中1万个数,这样的速度一定必单线程快。这种方式可以非常有效的发挥系统的性能;

​ 但是产品大大的需求是千变万化的,突然产品大大说, 把选中的10万个数全部加起来 。

​ 或者我的一个实战场景:人脸库中有10万左右个人脸,然后用户扫脸验证的时候,多线程比对10万个人脸的相似度,相似度超过0.8的要收集起来,然后全部线程对比完成之后,把所有超过0.8分数的人脸进行处理,分数最大人脸则认为是用户的人脸。

​ 我们知道多线程的start()方法之后是执行了run()方法,但是run()方法是没有返回值的,如果我们的知识只有这点的话,就很棘手了。

​ 像这种场景就需要我们异步编程来做了。异步编程处理之后,可以在线程执行之后的返回值供我们进行处理。

2.线程返回值

2.1 Callable接口

​ Callable接口和Runable接口及其相似,Callable中call()可以看作为Runable.run()方法的加强,提供了返回值,并且可以在执行过程中抛出异常。V代表返回值参数类型。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

2.2 ThreadPoolExecutor 类

​ 由于Callable 有返回值,所以在执行过程冲必须使用一个执行器来使用ThreadPoolExecutor的submit方法来提交任务,get方法获取任务返回值,如下:

public <T> Future<T> submit(Callable<T> task) ;
// 调用get方法 会一直阻塞,直到任务完成,所以建议使用有等待时间的方法
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

​ 如果get一直阻塞,可以使用boolean cancel(boolean mayInterruptIfRunning);进行任务取消,mayInterruptIfRunning表示是否允许发送中断来取消线程,返回值代表是否取消成功。boolean isCancelled();代表是否成功取消,在任务取消之后,如果再次执行get方法会抛异常;

​ boolean isDone();方法返回知否执行完毕,任务执行完毕、任务执行过程中抛出异常、任务取消都会返回true。

ThreadPoolExecutor构造器参数如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  • corePoolSize 线程池核心大小
  • maximumPoolSize 线程池最大线程数
  • keepAliveTime 和unit 空闲线程存活时间
  • workQueue 队列类型

2.3 代码示例

​ 了解有返回值的多线程之后,就可以处理上面的场景的,不过为了测试数量调小一点。

 public static void main(String[] args) {
        final int x=4000;
     // 创建执行器对象,参数下面解释
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 7, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10));
        List<Future<Integer>> list = new ArrayList<>();
     // 创建2个线程
        for(int y = 0;y<2;y++){
            Future<Integer> submit = executor.submit(() -> {
                Random random = new Random();
                Integer sum = 0;
                //  循环2次
                for(int i =0;i<2;i++){
                    int num = random.nextInt(10000);
                    if(num>x){
                        sum=sum+num;
                    }
                }
                System.out.println(sum);
               return sum;
             });
            list.add(submit);
        }
        int sum = 0;
        for(int y=0;y<list.size();y++){
            Future<Integer> integerFuture = list.get(y);
            try {
                sum=sum+integerFuture.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        System.out.println("总和:"+sum);
    }

​ 如上代码即可完成线程执行完成之后,返回值进行累加。

​ 问题来了,如上代码比较简单,线程间执行速度基本一致,但是如果出现线程间速度不一样的情况就会很大的效率问题。调用get() 方法之后,会一直阻塞,直到当前线程执行完毕为止,那么如果线程1线程执行内容比较多,但是线程2执行完毕,主线程也只能等待线程1返回之后再处理线程2。缺陷显而易见。

3.批量执行异步任务

3.1CompletionService接口

​ Future接口可以实现获取异步线程的执行结果,但是如果很多异步任务一起处理的话,实现起来会很繁琐。java.util.concurrent.CompletionService更好的解决了批量任务的处理。

// 提交任务
Future<V> submit(Callable<V> task);
//获取任务执行结果
Future<V> take() throws InterruptedException;
//获取任务执行结果 异步
Future<V> poll();
//获取任务执行结果 异步
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;

submit方法与ThreadPoolExecutor.submit方法效果类似;不同的是take()方法,CompletionService执行了几次submit()方法,就可以执行几次take()方法,如果take()执行的时候没有执行结束的异步任务,那么该线程就会被暂停,直到有异步任务执行结束。

ExecutorCompletionService构造器


public ExecutorCompletionService(Executor executor)
public ExecutorCompletionService(Executor executor,
                                     BlockingQueue<Future<V>> completionQueue)
    

通过构造方法很明显的看出,Executor负责执行异步任务,BlockingQueue负责接收任务执行的结果。

CompletionService接口和ThreadPoolExecutor并不存在继承关系,只是方法名称相似而已。ThreadPoolExecutor接口继承的Executor接口,就一个execute方法,这种抽象使我们不需要关注任务的具体逻辑,达到了解耦的效果。

public interface Executor {
    void execute(Runnable command);
}

3.2代码实例

优化上面实例中,性能问题。通过take方法,即可做到哪个线程执行速度快先处理哪个线程的返回内容。

 public static void main(String[] args) {
        final int x=4000;
        ExecutorService executorService = Executors.newCachedThreadPool();
        CompletionService completionService = new ExecutorCompletionService(executorService);
        // 创建执行器对象,参数下面解释
        // 创建2个线程
        for(int y = 0;y<2;y++   ){
             completionService.submit(() -> {
                Random random = new Random();
                Integer sum = 0;
                //  循环2次
                for(int i =0;i<2;i++){
                    int num = random.nextInt(10000);
                    if(num>x){
                        sum=sum+num;
                    }
                }
                System.out.println(sum);
                return sum;
            });
        }
        int sum = 0;
        for(int y=0;y<2;y++){
            Future<Integer> integerFuture = null;
            try {
                //通过take方法,即可做到哪个线程执行速度快先处理哪个线程的返回内容
                integerFuture = completionService.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                sum=sum+integerFuture.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
        System.out.println("总和:"+sum);
    }

猜你喜欢

转载自blog.csdn.net/qq_30285985/article/details/103915126