回答
在 Java 中, 创建线程的方式有四种:
- 继承 Thread 类
- 实现 Runnable
- 实现 Callable 接口,并结合 Future 实现
- 通过线程池创建线程
其实,归根到底只有两种,一个是继承 Thread 类,一个是实现 Runnable 接口,其他两种方式,其本质还是这两种方式。
扩展知识
继承 Thread 类
直接继承 Thread
类并重写其 run()
来创建线程。
public class MyThread extends Thread {
@Override
public void run() {
// do something()
}
}
public class Test {
public static void main(String[] args) {
new MyThread().start();
}
}
这种方式看起来简单,但是它的灵活性较差。由于 Java 不支持多重继承,一旦继承了 Thread
类,就无法继承其他类,导致我们无法对其进行扩展。
实现 Runnable
实现 Runnable 接口 ,并实现 run()
。
public class MyRunnable implements Runnable {
@Override
public void run() {
// do something()
}
}
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
}
}
相比继承 Thread 类,实现 Runnable 接口更加灵活。
实现 Callable 接口,并结合 Future 实现
- 定义一个 Callable 的实现类,并实现
call()
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码
// 计算结果并返回
return 123;
}
}
有两种方式来执行 Callable。
- 1、由于 Thread 对象只能执行 Runnable 任务,因此无法直接让 Thread 执行 Callable 任务,但是可以先将 Callable 封装成 FutureTask,而 FutureTask 是实现了 Runnable 接口的,所以 Thread 对象可以执行 FutureTask 任务:
public class ThreadTest {
public static void main(String[] args){
// 将Callable封装成FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
// 把 FutureTask 作为 Thread 类的参数 ,创建 Thread 线程对象。
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
// 通过FutureTask拿到执行结果
System.out.println(futureTask.get());
}
}
- 2、可以利用 ExecutorService 来实现。使用Executors 来创建一个ExecutorService 对象。利用 ExecutorService 的
submit()
来提交 Callable 任务,该方法返回一个Future对象,该对象可以用来获取Callable任务的结果。
public class ThreadTest {
public void main(String[] args) throws Exception {
// 创建ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交Callable任务并获取Future
Future<Integer> future = executor.submit(new MyCallable());
// 从Future获取结果
Integer result = future.get();
}
}
Runnable 和 Callable 的区别是什么?
- Runnable 的
run()
不返回任何值,而Callable的call()
方法可以返回一个值。 - Runnable的
run()
方法不能抛出受检查的异常。如果run()
方法内部需要处理异常,它必须在方法内部捕获并处理这些异常。而Callable的call()
方法可以抛出异常,因此可以将受检查的异常传递给调用者,由调用者来处理。
通过线程池创建线程
在Java中,使用线程池来创建和管理线程是一种比较好的、推荐的方式,线程池能够减少在创建和销毁线程上的开销,并且可以控制并发线程的数量。
我们可以创建线程池,然后将任务提交给线程池来执行:
public class ThreadTest {
public void main(String[] args) throws Exception {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 创建Runnable对象
Runnable runnable = () -> {
// do something()
};
// 提交任务给线程池
executor.submit(runnable);
}
}
题外话
在上面大明哥说过,从本质上来说 Java 创建线程的方式只有两种,一种是继承 Thread 类,一种是实现 Runnable 接口,我们从 Thread 类的源码也可以看出:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. The other way to create a thread is to declare a class that implements the Runnable interface.
其实说两种、四种、甚至三种(去网上搜索你会发现有些人写的是三种),大明哥觉得都没有错,关键在于自己能否说清楚,讲明白,让面试官信服。
如果换一种方式来问,比如在 Java 中有多少种方式来创建线程,那就比较多了,大明哥简单列几种:
- 继承 Thread 类
- 实现 Runnable 接口
- 匿名内部类
- 实现Callabe接口
- 定时器(java.util.Timer)
- 线程池
- 函数式
- Spring异步方法