Callable和Future详解 – 基于JDK 1.8

目录

1、多线程的实现方式

2、第三种创建线程方式

3、执行任务Demo

4、应用场景


1、多线程的实现方式

在JDK1.5以前,多线程的两种创建方式,实现Runnable接口,或者继承Thread类;其中Thread类也是实现了Runnable接口;重写run方法,完成多线程要执行的内容;如下源码可以看到,run方法可以执行完成任务,但是方法返回确实void类型;

class Thread implements Runnable {
}

public interface Runnable {
      public abstract void run();
}

2、第三种创建线程方式

为了解决多线程返回值的问题,在concurrent包中,有了第三种线程的实现方式:Callable(中译:可偿还的)接口,看翻译就知道不是执行一下那么简单,可重写call方法,获取线程执行返回值;

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

执行Callable以后,会通过Future(中译:未来)的get方法来获取返回值,看翻译就知道肯定跟后来有点什么关系:


public interface Future<V> {

      /**a、假设任务未开始,调用cancel(true/false)方法将返回false;
        b、假设任务已经开始,执行cancel(true)方法将以中断执行此任务线程
        的方式来试图停止,中断成功,返回true;
        c、假设当任务已经启动,执行cancel(false)方法将不会对正在执行的
        任务线程产生影响(让线程正常执行到完成),返回false;
        d、当任务已经完成,执行cancel(true/false)方法将返回false;
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**如果任务执行完成之前,被取消成功,则返回true
     */
    boolean isCancelled();

    /**a、线程正常执行完成,返回true;
      b、线程抛出异常,返回true;
      c、线程被取消,返回true;
     /
    boolean isDone();

    /* a、阻塞直等到线程执行任务完成返回计算结果返回计算结果;
     * b、如果任务被取消,抛出CancellationException;
     * c、如果任务计算中途异常,抛出ExecutionException;
     * d、如果线程被中断,抛出InterruptedException;
     */
    V get() throws InterruptedException, ExecutionException;

    /**参考上述get方法,唯一不同的是,加上了时间戳,超过时间戳未执
     行完成的话,抛出TimeoutException /
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

3、执行任务Demo

Future的唯一实现类是FutureTask,常见的方式,就是我们把Callable、FutureTask和线程池结合一起来使用:

/**
 * 测试Future和Callable
 */

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class TestFutureTaskDemo {

    //方便展示运行,这里写一个内部类实现Callable类,重写call方法
    private class CallableDemo implements Callable<Integer> {
        //线程执行结果
        private Integer result;
        //线程名称,方便查看多个线程执行进度
        private String  callableName;

        public CallableDemo(Integer result,String callableName) {
            this.result = result;
            this.callableName = callableName;
        }
        @Override
        public Integer call() throws Exception {
            result = result + 1;
            System.out.println("callableName:"
                +callableName+"; result :"+result);
            Thread.sleep(5*1000);
            return result;
        }
    }

    public static void main(String[] args) {

        //单个线程计算结果初始值
        Integer computeResult = 5;
        TestFutureTaskDemo task = new TestFutureTaskDemo();

        // 创建一个futureList来存放线程池提交Callable任务返回的Future对象;
        List<Future<Integer>> futureList = new ArrayList<Future<Integer>>();

        // 创建线程池,来执行Callable任务,核心线程大小数3;
        // 阿里巴巴Java编码规范,不推荐使用Executors来创建线程池;
        // 例如:Executors.newFixedThreadPool(3);
        // 原规则描述如下:【强制】线程池不允许使用 Executors
        // 去创建,而是通过 ThreadPoolExecutor 的方式,这样
        // 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

        ExecutorService executor =
                new ThreadPoolExecutor(3, 3,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

        //需要n个线程计算,n=6;
        for (int i = 0; i < 6; i++) {
            Future f = executor.submit(task.new
                 CallableDemo(computeResult,i+""));
            // f.cancel(false);
            futureList.add(f);
        }

        // 假设执行其它任务2秒过后,开始尝试中断线程
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            System.out.println("main thread InterruptedException");
        }
        //取消第一个执行中的线程
        //这里进行了cancel(true/false),
        //下面的 Integer temp = ft.get() 代码,就会抛            
        //CancellationException异常
        System.out.println("callable "+0+" is canceled:"
            +futureList.get(0).cancel(true));

        // 如果把上面的中断代码去掉
        //正常运行结果应该是5+(6*(5+1))=41
        for (Future<Integer> ft : futureList) {
            try {
                Integer temp = ft.get();
                computeResult = computeResult+temp;
            } catch (InterruptedException e) {
                System.out.println("demo1 InterruptedException");
            } catch (ExecutionException e) {
                System.out.println("demo1 ExecutionException");
            }
        }
        System.out.println("demo1 computeResult:"+computeResult);
        executor.shutdown();
    }
}

运行结果,因为任务被取消了,所以会抛出异常,注释掉任务取消的代码,可以尝试运行一下代码,那么会返回总执行结果Integer值41:

callableName:2; result :6
callableName:0; result :6
callableName:1; result :6
callable 0 is canceled:true
callableName:3; result :6
Exception in thread "main" java.util.concurrent.CancellationException
	at java.util.concurrent.FutureTask.report(FutureTask.java:121)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at interview.concurrent.TestFutureTaskDemo.main
            (TestFutureTaskDemo.java:74)
callableName:4; result :6
callableName:5; result :6

4、应用场景

由于Future是常用的多线程设计模式,因此在JDK中内置了Future模式的实现。这些类都在java.util.concurrent包里面。

对于Future模式来说,虽然它无法立即给你需要的数据。但是,它会返回给你一个契约,将来,你可以凭借这个契约去重新获取你需要的信息。

通过上面的例子,我们可以发现,Future模式,可以用于哪些应用场景呢?

 

(1)、在一些实际应用中,某些操作很耗时,但又不可或缺。APP浏览新闻时,先显示的是文字内容,而图片的下载肯定要比文字慢,图片重要性略低于文字,

可以用Future模式,文字和图片异步并行执行下载;

 

(2)购物时,查看以前的订单信息;可能要查许多关联数据:订单详情,积分信息,物流信息等,所以可以用Future做出异步多线程的计算后显示(有专门的CountDownLatch,貌似实现这种场景更合适一些);

 

(3)再者,查一个数据集合,第一页至第一百页,需要返回总结集。如果一次limit 0 10000,这样,一个SQL查询出非常慢。但用100个线程,一个线程只查limit0 10 就非常快了,利用多线程的特性,返回多个集合,再合并集合结果;(思考,查询10次可能和数据库交互10次I/O,真的就比一次查询快吗?当然这里只是举例一个可选择场景)

 

(4)、Future用于异步获取执行结果或者取消任务。在高并发场景下确保任务只执行一次。

在很多高并发的环境下,往往我们只需要某些任务只执行一次。这种使用情景FutureTask的特性恰能胜任。举一个例子,假设有一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。对于这样的应用场景,通常采用的方法为使用一个Map对象来存储key和连接池对应的对应关系,典型的代码如下面所示:

应用(4)场景参考贴如下:https://blog.csdn.net/linchunquan/article/details/22382487

 

下一篇:

FutureTask详解 –源码分析– 基于JDK 1.8

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值