Java多线程学习(Future接口)

目录

Future接口理论知识复习

Future接口

Future接口常用实现类FutureTask异步任务

Future接口能干什么

当前存在问题

Future接口相关架构

FutureTask的使用

Future的优缺点

其他方法

结论:

对于一些复杂应用

回调通知

异步任务

多个任务前后依赖可以组合处理

结论


一、Future接口理论知识复习

Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消,判断任务执行是否完毕等

案例说明:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙其他事情或者先执行完,过了一会采取获取子任务的执行结果或变更的任务状态

Future接口

可以为主线程开一个分支去做一些耗时的操作,专门为主线程处理耗时和费力的复杂业务

二、Future接口常用实现类FutureTask异步任务

1、Future接口能干什么

Future是java5新加的一个接口,提供了异步并行计算的功能。

如果主线程需要执行一个很耗时的计算任务,我们可以通过future把这个任务放到异步线程中去执行

主线程继续处理其他任务或者先行结束,再通过future获取计算结果

目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务

Runnable接口、Callable接口、Future接口和FutureTask实现类

简单说明:Runnable和Callable接口的区别-> Runnable没有返回值、不会抛出异常;Callable会有返回值会抛出异常

当前存在问题

异步线程需要满足三个特点:多线程、有返回值、异步任务

1、现在 Thread (Runnable target,String name ) 可以满足特点1

2、有返回值则使用callable

3、异步任务则需要实现future接口

所以如果能找到满足以上三个条件的线程是最好的,我们发现FutureTask(Callable<V>callable)可以很好的解决这个问题(FutureTask通过构造注入callable,使得多线程、异步的任务拥有了callable接口的特性,即有返回值)

2、Future接口相关架构

3、FutureTask的使用

正确使用FutureTask接口,如何使用FutureTask来调用callable接口,满足异步线程的规范并带回返回值

public class MyThread_01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //MyThreadCallable myThreadCallable = new MyThreadCallable();
        FutureTask<String> callableFutureTask = new FutureTask(new MyThreadCallable());
        //FutureTask callableFutureTask = new FutureTask(new MyThreadCallable());
        //FutureTask<String> callableFutureTask = new FutureTask<>(new MyThread_02());

        Thread t1 = new Thread(callableFutureTask, "t1");
        t1.start();

        String s = callableFutureTask.get();
        System.out.println("s = " + s);

    }

}

public class MyThreadCallable implements Callable<String> {

    /**
     *  Runnable 和 Callable 的区别
     *  1、是否抛出异常
     *  2、是否有返回值
     */
    @Override
    public String call() throws Exception {
        System.out.println(" thread.call");
        return "hello";    }
}

4、Future的优缺点

优点:Future+线程池异步多线程任务配合,能显著提高程序的执行效率

如果使用线程池和future获取异步任务的执行结果,则效率只会比单个线程同步执行效率提升1/3;但是如果不获取执行结果,则会比单个线程同步执行效率提升很多倍

public class FutureThreadPool {
    /**
     * 3个任务,开启多个异步任务来处理,耗时多久
     *      不获取结果   花费时间-->1050  效率显著提升
     *      获取结果     花费时间-->3048  效率能提升1/3左右
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws Exception {

        //花费时间-->4003
        //threadWay();
        long startTime = System.currentTimeMillis();
        //创建线程池,避免多个任务需要多次new 线程导致的资源的浪费,最好使用线程池使得线程做到线程的复用
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        //Query query = new Query();
        //ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.MILLISECONDS, (BlockingQueue<Runnable>) query);

        FutureTask<String> stringFutureTask = new FutureTask<String>(()->{
            TimeUnit.SECONDS.sleep(2);
            return "task1 over";
        });
        executorService.submit(stringFutureTask);

        //System.out.println("1--"+stringFutureTask.get());

        FutureTask<String> stringFutureTask2 = new FutureTask<String>(()->{
            TimeUnit.SECONDS.sleep(1);
            return "task2 over";
        });
        executorService.submit(stringFutureTask2);

        System.out.println("1-- " + stringFutureTask.get());
        System.out.println("2-- " + stringFutureTask2.get());

        /*MyThreadCallable myThreadCallable = new MyThreadCallable();
        Future<String> submit = executorService.submit(myThreadCallable);
        System.out.println("3--"+submit.get());*/

        TimeUnit.SECONDS.sleep(1);

        long endTime = System.currentTimeMillis();

        System.out.println("花费时间-->"+(endTime-startTime));
        System.out.println(Thread.currentThread().getName()+"\t"+"---end");

        executorService.shutdown();
    }

    /**
     * 3个任务,只有一个main来处理,耗时多久  花费时间-->4003
     * @throws Exception
     */
    public static void threadWay() throws Exception{
        long startTime = System.currentTimeMillis();

        TimeUnit.SECONDS.sleep(2);
        TimeUnit.SECONDS.sleep(1);
        TimeUnit.SECONDS.sleep(1);

        long endTime = System.currentTimeMillis();

        System.out.println("花费时间-->"+(endTime-startTime));
        System.out.println(Thread.currentThread().getName()+"\t"+"---end");
    }
}

缺点:

1、get方法容易阻塞,一般将get方法都放到程序的最后面,不影响其他程序的工作

package com.juc.thread_future;

import java.util.concurrent.*;

/**
 * @description 缺点展示
 * @author: shangqj
 * @date: 2023/4/11
 * @version: 1.0
 */
public class FutureThreadPool2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {


        /**
         * 线程池的4种创建方式   todo
         */
        //ExecutorService executorService = Executors.newFixedThreadPool(5);
        /**
         * 创建线程池的核心参数   todo
         *
         */
        //ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1000, TimeUnit.MILLISECONDS, null);


        FutureTask<String> stringFutureTask = new FutureTask<String>(()->{
            System.out.println(Thread.currentThread().getName()+"\t"+"come in ");
            TimeUnit.SECONDS.sleep(5);
            return "task1 over";
        });

        Thread thread = new Thread(stringFutureTask,"t1");
        thread.start();

        /**
         * 缺点一:get的方法的阻塞
         * 此处关于get方法的说明,如果
         *         System.out.println(Thread.currentThread().getName() + "忙其他的事情---");
         *
         *         System.out.println(stringFutureTask.get());
         *
         *         执行顺序是这样,则程序是可以被正常高效的执行的
         *
         *         如果执行顺序是这样
         *         System.out.println(stringFutureTask.get());
         *
         *         System.out.println(Thread.currentThread().getName() + "忙其他的事情---");
         *
         *         则程序的main程序不会执行其他任务,会等待get方法执行完成之后才会执行自己的任务
         */
        //主线程 执行其他事情
        System.out.println(Thread.currentThread().getName() + "忙其他的事情---");

        //获取线程的执行结果,容易造成程序的堵塞   一般将get放到最后面处理
        /**
         * get 小结:
         *  get容易程序堵塞
         *  假如不愿意等待很长时间,可以设置一个等待时间,超过等待时间就会抛出异常,一定程度上避免程序的阻塞
         */
        System.out.println(stringFutureTask.get());
        System.out.println(stringFutureTask.get(3,TimeUnit.SECONDS));       //-->设置等待时间

    }
}

缺点2:isDone()轮询容易导致cpu空转耗费更多的系统资源

我们一般使用get的时候不会单纯的使用get,会和isDone结合使用,

        /**
         * isDone 方法
         *  轮询的方式会耗费无畏的cpu资源,而且不见得能及时的处理计算结果
         *  如果想要异步获取结果,通常会以轮询的方式获取结果,尽量不要堵塞
         */
        while (true){
            if (stringFutureTask.isDone()){
                stringFutureTask.get();
                System.out.println(stringFutureTask.get());
                break;
            }else {
                //如果没有完成 则每秒钟判断一次是否完成了
                TimeUnit.SECONDS.sleep(1);
                System.out.println("任务正在执行中");
            }
        }

其他方法
boolean cancel(boolean mayInterruptIfRunning);// 取消任务
boolean isCanclled();// 是否已经取消
boolean isDone();// 是否已经完成
V get(long timeout,TimeUnit unit);// 超时时间内获取结果

结论:

future接口对于结果的获取方式并不是很友好,只能通过阻塞轮询的方式得到任务的结果

5、对于一些复杂应用

对于简单的业务场景使用future完全是ok的

回调通知

对于future的任务完成之后,可以在完成之后告诉我们,也就是我们的回调通知,future接口是没有办法满足的

通过轮询的方式去判断任务是否完成这样非常占用cpu且代码不够优雅 -> 回调通知代替

异步任务

future + 线程池配合使用

多个任务前后依赖可以组合处理
  • 想要将多个异步任务的计算结果结合起来,后一个异步任务的计算洁后果需要前一个异步任务的值
  • 将两个或多个异步计算合成一个异步计算的结果,这几个异步任务计算相互独立,同时后面这个又依赖前一个的处理结果

以上两种场景future接口也是没有办法满足的

结论

对于future接口无法满足的复杂应用我们需要使用CompletableFuture接口来满足

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值