大家好,我是风筝。
提到 Java 就不得不说多线程了,就算你不想说,面试官也得让你说呀,对不对。那说到多线程,就不得提线程了(这不废话吗)。那说到线程,就不得不说Runnable
和Callable
这两个家伙了。
说熟悉也是真熟悉,在刚学习多线程的时候,第一个例子大概就是下面这样子的。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行线程" + Thread.currentThread().getName());
}
}).start();
看到了 Runnable
的身影,有时候还会看到Callable
的。
但是说很熟悉吧,印象也不是很大,好像就用了一下这两位的名号,然后剩下的部分就跟他俩没啥关系了。
今天,我们就来看看这两位到底是什么,有什么区别,什时候应该用 Runnable
,什么时候又应该用 Callable
。
Runnable
自从 Java 诞生,Runnable
就存在了,元老中的元老了,在 1.5 之前,如果你想使用线程,那必须要实现自 Runnable
。因为到了 JDK1.5,JDK 才加入了Callable
。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
是不是接口非常简单,就一个抽象方法。
其实还可以再简化一下, @FunctionalInterface
标明这个接口是一个函数式接口。函数式接口是在 JDK8 才加入的,为的就是实现函数式编程,就那种 Lambada 表达式。
所以在 JDK1.7 中,是没有@FunctionalInterface
修饰的,简简单单。我们找到 JDK1.7 的 Ruunable
实现,是下面这样子
public interface Runnable {
public abstract void run();
}
想了解更多函数式编程的话,可以看这篇文章:Lambda、函数式接口、Stream 一次性全给你
如果一个线程类要实现 Runnable
接口,则这个类必须定义一个名为 run
的无参数方法。
实现了 Runnable
接口的类可以通过实例化一个 Thread
实例,并将自身作为目标传递来运行。
举个例子
首先定义一个RunnableThread
类,并实现自(implements)Runnable
接口,然后重写 run
方法。
public class RunnableThread implements Runnable{
@Override
public void run() {
System.out.println("当前线程名称"+ Thread.currentThread().getName());
}
}
使用RunnableThread
作为线程类(Thread)实例化的参数,然后调用run
方法。
RunnableThread runnableThread = new RunnableThread();
Thread thread = new Thread(runnableThread);
thread.start();
注意,是调用新 new 出来的 Thread 实例的start()
方法,不要调用run
方法,虽然我们是重写Runnable
的 run
方法的。调用 run
方法并没有创建线程的效果,而是直接在当前线程执行,就和执行一个普通类的普通方法一模一样。
为什么要调用 start()
方法呢,我们看看 Thread
的 start()
方法实现中,其实是调用了一个名称为 start0()
的 native 方法,native 方法就不是用 Java 实现的了,而是在 JVM 层面的实现。
这个start0
方法的主要逻辑就是启动一个操作系统线程,并和 JVM 线程绑定,开辟一些空间来存储线程状态和上下文的数据,然后执行绑定的 JVM 线程(也就是我们实现了 Runnable 的类)的 run
方法的代码块,从而执行我们自定义的逻辑。
还可以用线程池的方式调用
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("thread-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(runnableThread);
singleThreadPool.shutdown();
如果 Runnable
那么完美的话,就没必要在 JDK1.5 中加入和它超级相似的 Callable
了。
Runnable
有什么不完美的地方吗?就是它的 run
方法是没有返回值的。
如果你想在主线程中拿到新开启线程的返回值的话,Runnable
就不太方便了。必须要借助共享变量来完成。
所以,如果你的场景是要有返回值的话, 就要 Callable
出手了。
Callable
Callable
是在 JDK1.5 才加入的,为的就是弥补 Runnable
没有返回值的缺陷,虽然绝大多数场景都可以用 Runnable
来实现。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
和 Runnable
类似的,@FunctionalInterface
也是后来加入的,可以不考虑,只是为了函数式写法。
Callable
接口只有一个 call
方法,并且有一个泛型返回值,可以返回任何类型。
举个例子
首先声明一个类,实现自 Callable
接口,返回值为字符串类型
public class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
return "线程名称:" + Thread.currentThread().getName();
}
}
在代码中通过下面的方式调用
CallableThread callableThread = new CallableThread();
FutureTask<String> futureTask = new FutureTask<>(callableThread);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println("执行结果= " + result);
看上去就比 Runnable
要复杂一点,要借助FutureTask
了,因为 Thread
类没有接受Callable
的构造函数。
使用 FutureTask.get()
方法获取执行结果。
在日常的开发中,不建议直接这样用,除非你明确的知道这样做没有问题,否则的话,推荐使用线程池的方式来使用。
CallableThread callableThread = new CallableThread();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(callableThread);
String result = future.get();
System.out.println("任务执行结果: " + result);
executor.shutdown();
如何选择用哪一个
取舍的基本原则就是需不需要返回值,如果不需要返回值,那直接就选 Runnable
,不用犹豫。如果有返回值的话,那更不用犹豫,不要想着借助共享变量的方式。
另外还有一点就是是否需要抛出异常, Runnable
是不接受抛出异常的,Callable
可以抛出异常。
Runnable
适合那种纯异步的处理逻辑。比如每天定时计算报表,将报表存储到数据库或者其他地方,只是要计算,不需要马上展示,展示内容是在其他的方法中单独获取的。
比如那些非核心的功能,当核心流程执行完毕后,非核心功能就自己去执行吧,至于成不成功的,不是特别重要。例如一个购物下单流程,下单、减库存、加到用户的订单列表、扣款是核心功能,之后的发送 APP 通知、短信通知这些就启动新线程去干去吧。
最后
Runnable
在 java.lang
这个包下,而当 JDK1.5 发布的时候,新加入的 Callable
被安置在了 java.util.concurrent
这个包下,这是 Java 里有名的并发编程相关包,各种锁啊、多线程工具类啊,都被放在这个包下。按道理,Runnable
也应该在这里才对。
可见再厉害的项目也是随着项目的扩大而慢慢的规划,而前期的一些看似不太合理的地方,只能做兼容和妥协。