一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
简介
Java 中,创建线程的常见方式有以下三种:
- 继承
Thread类创建线程 - 实现
Runnable接口创建线程 - 通过
FutureTask类和Callable接口创建线程
团子注:有的人可能会觉得上述划分多余了,本质上只有一种通过
Thread类创建线程的方式;也有人认为分少了,线程池和定时器等也能创建线程。个人认为这是一种便于理解的划分,不用太过认真,否则就变成研究“茴”字有多少种写法这种课题了。
继承 Thread 类创建线程
继承 Thread 类创建线程,主要步骤如下:
- 继承
Thread类 - 重写
run()方法
run()方法体常被称作线程执行体,表示一个线程需要完成的任务和逻辑。
示例代码如下:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("hello my thread!");
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
// hello my thread!
}
}
如果线程执行体不需要复用,也可以采用匿名内部类的形式编写 Thread 类的子类,上述代码的等价形式如下:
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
System.out.println("hello my thread!");
}
};
t.start();
// hello my thread!
}
}
当然,还可以利用 Java 8 特性,进一步简化成 Lambda 表达式的形式,上述代码的等价形式如下:
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("hello my thread!"));
t.start();
// hello my thread!
}
}
实现 Runnable 接口创建线程
Runnable 接口是一个函数式接口,位于 java.lang 包中,它的源码如下:
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
实现 Runnable 接口创建线程,主要步骤如下:
- 实现
Runnable接口的run()方法 - 使用
Thread类创建线程,传入Runnable接口对象
下面的代码定义了 Runnable 接口的实现类,实现其 run() 方法,并给 Thread 类的构造函数传入了 Runnable 接口对象。
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("hello my thread!");
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new MyThread();
Thread t = new Thread(runnable);
t.start();
// hello my thread!
}
}
同样,也可以采用匿名内部类、Lambda 表达式的形式改写上述代码。
下面是用匿名内部类的形式实现的 Runnable 接口:
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello my thread!");
}
});
t.start();
// hello my thread!
}
}
下面的代码则采用了 Lambda 表达式,进一步简化代码:
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("hello my thread!"));
t.start();
// hello my thread!
}
}
通过 FutureTask 类和 Callable 接口创建线程
Callable 接口与 Runnable 接口都是函数式接口,它们的不同之处在于:
Callable接口的call()方法是线程执行体,而Runnable接口的run()方法是线程执行体Callable接口的call()方法带返回值,声明抛出Exception异常
Callable 接口的源码如下:
package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
通过 FutureTask 类和 Callable 接口创建线程的步骤如下:
- 实现
Callable接口的call()方法 - 创建
FutureTask类的对象,传入Callable接口对象 - 创建
Thread类的对象,传入FutureTask对象
示例代码如下,采用了最为简洁的 Lambda 表达式来实现 Callable 接口对象:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> 666);
Thread thread = new Thread(task);
thread.start();
Integer result = task.get();
System.out.println(result);
// 666
}
}
创建线程的本质
创建线程的本质在于 Thread 类的 run() 方法,源码如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
在启动线程时,要调用线程对象的 start() 方法,而 start() 方法会调用原生的 start0() 方法,该方法会开启新线程,帮我们调用 Thread 对象的 run() 方法。根据上面的源码,执行流程分为两种情况:
- 如果是通过继承
Thread类创建的线程,会调用子类重写的run()方法 - 如果为
Thread类的构造函数传入了Runnable接口对象,则会调用接口对象target的run()方法。
几种创建线程方式的比较
- 通过继承
Thread类创建线程的优点在于,可以直接在run()方法中通过this获取当前线程对象,不像其他方式要调用Thread.currentThread()才能获取。这种方式的缺点在于,Java不支持类的多继承,Thread类的子类无法继承其他类。 - 通过接口实现类创建线程的优点在于,实现了线程执行体与
Thread类的解耦,职责分明。 - 一般推荐使用后两种创建线程的方案。