在 Java 中,创建并启动一个新的线程通常有两种主要的方式:继承 Thread 类和实现 Runnable 接口。
- 通过继承 Thread 类来创建一个新的线程类,并重写 Thread 类的 run() 方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); //启动线程
}
}
- 通过实现 Runnable 接口并实现其 run() 方法,然后将 Runnable 实例传递给 Thread 对象。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
}
}
特性 | 继承 Thread 类 | 实现 Runnable 接口 |
---|---|---|
继承 | 不支持多继承,限制较大 | 支持多继承,更灵活 |
代码解耦 | 线程逻辑和线程控制耦合 | 线程逻辑和线程控制分离 |
复用性 | 不易复用 | 更易复用 |
共享资源 | 需要额外处理 | 自然适应共享资源 |
ps:若两个同时使用呢?
class BothRunnableThread {
public static void main(String[] args) {
// 创建一个匿名内部类实现 Runnable 接口的 run 方法
new Thread(new Runnable() {
@Override
public void run() {
// 这个 run 方法来自于 Runnable 接口
System.out.println("I am runnable");
}
}) {
// 创建一个匿名内部类继承 Thread 类并重写其 run 方法
@Override
public void run() {
// 这个 run 方法来自于 Thread 类
System.out.println("I am thread");
}
}.start(); // 启动线程
}
}
结果是:
原因:
创建了一个实现了 Runnable 接口的匿名内部类的对象,然后这个Runnable类的匿名内部类的对象被传给Thread的target。
但是Thread的下面三行run方法被重写了,也就不用管Runnable类的target了,虽然Runnable类的target的run方法被重写了,所以没机会执行Runnable类的target的run方法了。
所以本质上创建新线程都是通过new Thread()创建新线程对象,然后使用它的start()方法启动线程,第一种继承Thread然后重写Thread的run()方法也不例外,因为只是重写run()方法而已。
至于基于callable接口的方式,最终是要把实现了callable接口的实例对象通过FutureTask(实现了Future和Runnable接口)包装成Runnable任务,再交给Thread去执行,所以这个其实可以和实习Runnable接口看成同一类。
实例代码:
package study;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class day03_runnable和callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Task1 task1 = new Task1();
new Thread(task1).start();
Task2 task2 = new Task2(10);
FutureTask<Long> futureTask = new FutureTask<Long>(task2);
new Thread(futureTask).start(); // FutureTask实现了Runnable, 所以可以传入Thread的构造函数
Long result = futureTask.get(); // 死等计算完成结果
System.out.println(result);
}
}
class Task1 implements Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
}
class Task2 implements Callable<Long> {
private long num;
public Task2(int num) {
this.num = num;
}
@Override
public Long call() throws Exception {
return this.num + 10;
}
}