并发编程大师系列之:Future、FutureTask和Callable

/ 并发编程 / 4 条评论 / 414人围观

鸣哥唱的那是真叫一个好!谁听谁怀孕

Callable与Runnable

Runnable

创建线程的两种方式:

继承Thread类和实现Runnable接口都可以,这2种方式都有一个缺陷就是在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式,这样使用起来比较复杂。

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

Runnable是 lang 包下的接口,它是无返回值的,线程在执行完任务后,无法返回任何的结果。并且没有抛出任何异常,也就是它的实现类无法抛出任何异常。

Callable

Callable位于 concurrent 包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做 call()。

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;
}

这是一个泛型接口,call() 函数返回的类型就是传递进来的V类型。

简单用法:

  1. Callable接口的实现类:
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "callable call 执行!";
    }
}
  1. 启动线程(需要借助FutureTask包装,也可以利用线程池ExecutorService)
MyCallable uc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(uc);
new Tread(ft).start();
// 获取返回值
System.out.println(ft.get());

Future

重点来了:Future 是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

同时Future 接口也位于concurrent 包下,拥有五个牛叉方法。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  1. cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  2. isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  3. isDone方法表示任务是否已经完成,若任务完成,则返回true。
  4. get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回。
  5. get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,则会抛出TimeOut异常,并不会直接返回null,需要手动判定一下(大神海子可能是误写了)。

从方法描述来看,Future具有三种别人不具备的功能:

Callable结合Future获取执行结果

场景描述:现计算一个数据,而这个数据的计算比较耗时,而我们后面的程序也要用到这个数据结果,那么这个时就用到Callable了,可以开设一个线程去执行计算,而主线程继续做其他事,而后面需要使用到这个数据时,再使用Future获取就OK。

/**
 * @author 70KG
 * @Title: CallableTest
 * @Description: 测试future
 * @date 2018/7/28上午10:44
 * @From www.nmyswls.com
 */
public class CallableTest {

    @Test
    public void test01() {
        //创建线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        //创建Callable对象任务
        CallableThread calTask = new CallableThread();
        //提交任务并获取执行结果
        Future<Integer> future = es.submit(calTask);
        //关闭线程池(发起一个关闭请求,已提交的任务会执行,但不会接受新的任务请求了。)
        es.shutdown();
        try {
            Thread.sleep(2000);
            System.out.println("主线程在执行其他任务");

            // 在需要用到子线程执行结果的时候调用get方法,
            // 该方法会阻塞其他包括主线程的执行
            if (future.get() != null) {
                //输出获取到的结果
                System.out.println("future.get()-->" + future.get());
            } else {
                //输出获取到的结果
                System.out.println("future.get()未获取到结果");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程在执行完成");
    }


    public class CallableThread implements Callable<Integer> {

        private int sum;

        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");
            Thread.sleep(2000);
            for (int i = 1; i <= 100; i++) {
                sum = sum + i;
            }
            System.out.println("Callable子线程计算结束!");
            return sum;
        }
    }

}

运行的结果:

Callable子线程开始计算!
Callable子线程计算结束!
主线程在执行其他任务
future.get()-->5050
主线程在执行完成

FutureTask

Callable结合FutureTask获取执行结果

用线程池:

/**
 * @author 70KG
 * @Title: CallableTest
 * @Description: 测试future
 * @date 2018/7/28上午10:44
 * @From www.nmyswls.com
 */
public class CallableTest {

    @Test
    public void test01() {
        //创建线程池
        ExecutorService es = Executors.newSingleThreadExecutor();
        //创建Callable对象任务
        CallableThread calTask = new CallableThread();
        // 创建FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(calTask);
        es.submit(futureTask);
        //关闭线程池(发起一个关闭请求,已提交的任务会执行,但不会接受新的任务请求了。)
        es.shutdown();
        try {
            Thread.sleep(2000);
            System.out.println("主线程在执行其他任务");

            // 在需要用到子线程执行结果的时候调用get方法,
            // 该方法会阻塞其他包括主线程的执行
            if (futureTask.get() != null) {
                //输出获取到的结果
                System.out.println("futureTask.get()-->" + futureTask.get());
            } else {
                //输出获取到的结果
                System.out.println("futureTask.get()未获取到结果");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程在执行完成");
    }


    public class CallableThread implements Callable<Integer> {

        private int sum;

        @Override
        public Integer call() throws Exception {
            System.out.println("Callable子线程开始计算!");
            Thread.sleep(2000);
            for (int i = 1; i <= 100; i++) {
                sum = sum + i;
            }
            System.out.println("Callable子线程计算结束!");
            return sum;
        }
    }

}

运行结果:

Callable子线程开始计算!
主线程在执行其他任务
Callable子线程计算结束!
futureTask.get()-->5050
主线程在执行完成
  1. 忘记本身就是一件不可能的事情,别妄想了。

    回复

    @test 27.19.226.35这位同学,把我服务器搞崩了,你咋做到的 ^_^

    回复

    @70KG 留言就崩了啊

    回复
  2. 永远相信美好的事情,即将发生

    回复