Java多线程 - Runnable接口和Callable接口的区别


Runnable接口源码:

@FunctionalInterface
public interface Runnable {
    // 这个run()方法返回值为void,没有受检异常的异常声明,出现异常只能内部捕获
    public abstract void run();
}

Callable接口源码:

@FunctionalInterface
public interface Callable<V> {
	// 这个call()方法有返回值,且声明了受检异常,可以直接抛出Exception异常
    V call() throws Exception;
}

Runnable接口和Callable接口的区别:

(1) Runnable接口中的唯一抽象方法run()方法没有返回值,Callable接口中的唯一抽象方法call()方法有返回值;

(2) Runnable接口的run()方法没有受检异常的异常声明,即异常只能在内部捕获,不能继续上抛, Callable接口的call()方法声明了受检异常,可直接抛出Exception异常,并且可以不予捕获;

(3) Callable实例不能和Runnable实例一样,直接作为Thread线程实例的target来使用;

(4) 异步执行任务在大多数情况下是通过线程池去提交的,而Runnable接口和Callable接口都可以应用于线程池;

(5) Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

注意:异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交(即通过Thread类的方式执行start()方法)。

下面实例解析以上不同点:

1. Runnable接口实例

首先,通过实现Runnable接口的方式创建一个异步执行任务:

public class RunnableTask implements Runnable  {
    // 线程体:需要线程异步执行的任务
    @Override
    public void run() {
        System.out.println("实现Runnable接口来编写异步执行任务");

        // run()方法内出现异常,只能捕获不能直接抛出
        try {
            Thread.sleep(1000) ;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

方式1:通过Thread类创建线程,将Runnable接口的实现类作为Thread线程实例的target来使用,执行异步任务

public class RunnableDemo {
    public static void main(String[] args) {
        Runnable target = new RunnableTask();
        // 通过Thread类创建一个新线程,并启动
        Thread thread = new Thread(target);
        thread.start();
    }
}

方式2:通过线程池创建线程,并提交异步执行任务

public class RunnableDemo2 {
    // 通过线程池创建3个线程
    private static ExecutorService executorService = Executors.newFixedThreadPool(3);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1、通过线程池的execute()方法提交异步执行任务,没有返回结果
        executorService.execute(new RunnableTaskDemo());

        // 2、通过线程池的submit()方法提交异步执行任务,有返回结果
        Future<?> submit = executorService.submit(new RunnableTaskDemo());
        // 获取返回结果
        System.out.println(submit.get()); // null
    }
}

2. Callable接口原理

Callable接口实例没有办法作为Thread线程实例的target来使用。既然如此,那么该如何使用Callable接口创建线程呢?因此需要一个在Callable接口与Thread线程之间起到搭桥作用的重要接口。

1、RunnableFuture接口源码:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture接口实现了2个目标:

(1) RunnableFuture通过继承Runnable接口,从而保证了其实例可以作为Thread线程实例的target目标;
(2) RunnableFuture通过继承Future接口,从而保证了可以获取未来的异步执行结果。

那有些同学可能疑惑了,Future接口又是什么?Future接口至少提供了3大功能:

(1) 能够取消异步执行中的任务;
(2) 判断异步任务是否执行完成;
(3) 获取异步任务完成后的执行结果;

2、Future接口的源码:

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

Future接口中的方法:

  • get():获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完成,其异步结果返回给调用线程。

  • get(Long timeout,TimeUnit unit):设置时限,(调用线程)阻塞性地获取异步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长限制,不会无限制地阻塞和等待,如果其阻塞时间超过设定的timeout时间,该方法将抛出异常,调用线程可捕获此异常。

  • boolean isDone():获取异步任务的执行状态。如果任务执行结束,就返回true。

  • boolean isCancelled():获取异步任务的取消状态。如果任务完成前被取消,就返回true。

  • boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。

总体来说,Future是一个对异步任务进行交互、操作的接口。但是Future仅仅是一个接口,通过它没有办法直接完成对异步任务的操作,JDK提供了一个默认的实现类——FutureTask。

3、FutureTask类 :

RunnableFuture只是一个接口,无法直接创建对象,如果需要创建对象,就需用到它的实现类——FutureTask。所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口,因此它可以作为Thread线程实例的target目标,也可以获取异步执行结果;

public class FutureTask<V> implements RunnableFuture<V> {
    
}

FutureTask内部有一个Callable类型的成员——callable实例属性:

private Callable<V> callable;

callable实例属性用来保存并发执行的Callable类型的任务,并且callable实例属性需要在FutureTask实例构造时进行初始化:

public FutureTask(Callable<V> callable) {
    if (callable == null) throw new NullPointerException();
    // callable实例属性需要在FutureTask实例构造时进行初始化
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

FutureTask类实现了Runnable接口,在其run()方法的实现版本中会执行callable成员的call()方法:

public void run() {
    if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 在run()方法中执行call()方法
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                // ...
            }
            if (ran) set(result);
        }
    } finally {
        // ...
    }
}

FutureTask内部还有另一个非常重要的Object类型的成员——outcome实例属性:

private Object outcome;

FutureTask的outcome实例属性用于保存callable成员call()方法的异步执行结果。在FutureTask类的run()方法完成callable成员的call()方法的执行之后,其结果将被保存在outcome实例属性中,供FutureTask类的get()方法获取。
在这里插入图片描述

3. Callnable接口实例

首先,通过实现Runnable接口的方式创建一个异步执行任务:

public class CallableTaskDemo implements Callable {
    // call()方法有返回值,并且可以抛出Exception异常
    @Override
    public String call() throws Exception {
        System.out.println("实现Callable接口来编写异步执行任务");
        Thread.sleep(1000);
        return "返回线程执行结果";
    }
}

方式1:通过Thread类创建线程执行异步任务

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建异步执任务实例
        Callable callable = new CallableTaskDemo();
        // 初始化callable实例属性
        FutureTask futureTask = new FutureTask(callable);
        // 创建线程执行异步任务
        Thread thread = new Thread(futureTask);
        thread.start();

        System.out.println("获取异步执行任务结果:"+futureTask.get());// 获取异步执行任务结果:返回线程执行结果
    }
}

方式2:通过线程池创建线程,并提交异步执行任务:

public class CallableDemo2 {
    // 通过线程池创建3个线程
    private static ExecutorService executorService = Executors.newFixedThreadPool(3);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 通过线程池的submit()方法提交异步执行任务,有返回结果
        Future submit = executorService.submit(new CallableTaskDemo());
        // 获取返回结果
        System.out.println(submit.get());
    }
}

4. FutureTask是什么?

Future是一个对异步任务进行交互、操作的接口。但是Future仅仅是一个接口,通过它没有办法直接完成对异步任务的操作,JDK提供了一个默认的实现类——FutureTask。

对于Calleble来说,Future和FutureTask均可以用来获取任务执行结果,不过Future是个接口,FutureTask是Future的具体实现,而且FutureTask还间接实现了Runnable接口,也就是说FutureTask可以作为Runnable任务提交给线程池。

5. 线程池中 submit() 和 execute() 方法有什么区别?

两者都是将一个线程任务添加到线程池中并执行;
1、excutor没有返回值,submit有返回值,并且返回执行结果Future对象
2、excutor不能提交Callable任务,只能提交Runnable任务,submit两者任务都可以提交
3、在submit中提交Runnable任务,会返回执行结果Future对象,但是Future调用get方法将返回null(Runnable没有返回值)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我一直在流浪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值