[JAVA] Java 并发之从 Runnable 和 Callable 浅入线程的创建方式
线程的创建方式
当被问到 Java 中创建线程的方式,可能一般就会想到两种方式:一种是继承 Thread 类,一种是实现 Runnable 接口。当然经常使用多线程开发的秃头可能还会想到使用线程池创建或者实现 Callable 接口。
下面就用示例代码,介绍这几种线程的创建方式:
Thread
public class MyThread extends Thread {
@Override
public void run() {
// do something
}
}
// 调用方式
// new MyThread().start();
Runnable
public class MyRunnable implements Runnable {
@Override
public void run() {
// do something
}
}
// 调用方式
// new Thread(new MyRunnable()).start();
Callable
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
// do something
int result = 10;
return result;
}
}
// 调用方式
// Callable 需要结合 FutureTask 或线程池(内部还是Future)使用
// var task = new FutureTask(new MyCallable());
// new Thread(task);
// 通过 task.get() 可以获取返回值
线程池
var es = Executors.newSingleThreadExecutor();
es.submit(new Runnable() {
@Override
public void run() {
// do something
}
});
实际上这些所谓的线程创建方式,不管哪种,最后都是要通过 Thread 调用完成线程的创建和运行(线程池调用虽然没直观的展示,但深入源码可以发现,线程工厂 DefaultThreadFactory 本质还是通过 new Thread 创建线程,这里不做展开)。
Runnable 和 Callable 的区别
从使用上看,最直观的区别就是实现需要重写的方法,一个有返回值,一个没有返回值。而且 Callable 的调用似乎更复杂一点。
先看一下源码:
// java.lang.Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// java.util.concurrent.Callable
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
两者其实都只是函数式接口,Runnable 甚至还不是 J.U.C 提供的。它们本身和线程是没有关系的,只是提供任务逻辑,交给 Thread 执行。
所以 Thread 还也可以更简单直接的给它传递一个匿名函数:
new Thread(() -> {
// do something
}).start();
Callable 因为有返回值,所以使用稍微麻烦一点,一般可以结合 FutureTask 调用。
// java.util.concurrent.FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
...
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
...
}
...
}
FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 接口继承了 Runnable 接口,所以 FutureTask 可以直接像 Runnable 那样传递给 Thread。
// java.util.concurrent.RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
关于继承 Thread
一般是不建议通过继承 Thread 类来实现线程。
使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走完生命周期被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果执行的内容比较少,比如只是在 run()
方法里简单打印一行文字,那么它带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run()
方法打印文字本身带来的开销要大得多。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,从而降低了性能开销。
Java 中类不支持多继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码的可拓展性。