回答
在 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异步方法
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。