06_Callable接口

Thread类、Runnable接口使得多线程编程简单直接。

但Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。

public void run()方法规范意味着你必须捕获并处理检查型异常。即使你小心捕获异常,也不能保证这个类(Runnable对象)的所有使用者都读取异常信息。

以上两个问题现在都得到了解决。从java5开始,提供了Callable接口,是Runable接口的增强版。用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。

1. Callable和Runable对比

先初步认识一下Callable接口:这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。

 

具体代码实现对比

class MyRunnableThread implements Runnable{
    @Override
    public void run() {

    }
}

class MyCallableThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        return null;
    }
}

public class CallableDemo {

    public static void main(String[] args) {
        // 创建多线程
    }
}

该如何使用Callable创建Thread对象,如果使用Runnable是:

public class CallableDemo {

    public static void main(String[] args) {
        // 创建多线程
        new Thread(new MyRunnableThread(), "threadName").start();
    }
}

现在能不能直接把MyRunnableThread换成MyCallableThread。当然不行,thread的构造方法参数需要Runnable类型的数据模型,而MyCallableThread属于Callable类型的。

那么到底怎么使用Callable创建thread对象呢?

2. Callable的使用

使用步骤:

  1. 创建Callable的实现类,并重写call()方法,该方法为线程执行体,并且该方法有返回值

  2. 创建Callable的实例。

  3. 实例化FutureTask类,参数为Callable接口实现类的对象,FutureTask封装了Callable对象call()方法的返回值

  4. 创建多线程Thread对象来启动线程,参数为FutureTask对象。

  5. 通过FutureTask类的对象的get()方法来获取线程结束后的返回值

这里出现了一个FutureTask,先认识该类,上源码:

发现:FutureTask其实充当了一个中间人的角色

/**
 * 1. 创建Callable的实现类,并重写call()方法,该方法为线程执行体,并且该方法有返回值
 */
class MyCallableThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "执行了!");
        return 200;
    }
}

public class CallableDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 2. 创建Callable的实例,并用FutureTask类来包装Callable对象
        // 3. 创建FutureTask对象,需要一个Callable类型的参数
        FutureTask task = new FutureTask<Integer>(new MyCallableThread());
        // 4. 创建多线程,由于FutureTask的本质是Runnable的实现类,所以第一个参数可以直接使用task
        new Thread(task, "threadName").start();
        //new Thread(task, "threadName2").start();
		
        /*while (!task.isDone()) {
            System.out.println("wait...");
        }*/
        System.out.println(task.get());
        System.out.println(Thread.currentThread().getName() + " over!");
    }
}

创建方式一

//1、创建callable任务
Callable<String> c = () -> {
		Thread.sleep(3000);
		System.out.println(Thread.currentThread().getName() + " 执行了Callable任务");
		return "hehe";
};
//2、创建FutureTask(Runnable的子类)
FutureTask<String> futureTask = new FutureTask(c);
//3、创建线程传入FutureTask并启动线程
new Thread(futureTask, "ff").start();

创建方式二

FutureTask<String> f1 = new FutureTask<>(() -> {
		System.out.println("callable任务执行了");
		return "hehe:" + new Date();
});
FutureTask<String> f2 = new FutureTask<>(() -> {
		System.out.println("callable任务执行了");
		return "hehe:" + new Date();
});
new Thread(f1).start();
new Thread(f2).start();
  • FutureTask:未来的任务,用它就干一件事,异步调用。通常用它解决耗时任务,挂起堵塞问题。
  • 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  • FutureTask 仅在 call 方法完成时才能get结果;如果计算尚未完成,则阻塞 get 方法。
  • 一旦计算完成,就不能再重新开始或取消计算。get方法获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。

注意:

  1. 为了防止主线程阻塞,建议get方法放到最后

  2. 只计算一次,FutureTask 会复用之前计算过得结果

创建多个线程,会怎样?

运行结果:依然只有一个就是threadName。

如果想打印threadName2的结果,即不想复用之前的计算结果。怎么办?再创建一个FutureTask对象即可。

猜你喜欢

转载自blog.csdn.net/qq_45037155/article/details/130412994