第一种:继承Thread类
// 1.创建一个继承 Thread 的类
class MyThread extends Thread {
// 2.重写 Thread 的 run() 方法
@Override
public void run() {
for (int i = 0; i < 100; i++) System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
// 3.创建 Thread 的子类对象
MyThread myThread = new MyThread();
// 4.调用子类对象的 start() 方法
myThread.start();
}
}
Thread
类中的两个重要的方法(其他的方法都很简单,见名知意):
-
yield()
:主动放弃执行权,让 CPU 随便再选一个线程来执行(当然可能 CPU 还是会选中我)。 -
join()
:让贤,在A
线程中调用B.join()
,相当于主动让B
线程排到自己前面去,自己则一直等到B
执行完才继续执行(进入阻塞状态直到B
线程执行结束)。
第二种:实现Runnable接口
// 1.创建一个实现 Runnable 接口的类
class MyRunnable implements Runnable {
// 2.实现 Runnable 的抽象 run() 方法
@Override
public void run() {
for (int i = 0; i < 100; i++) System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
// 3.创建实现了 Runnable 的类的对象
MyRunnable myRunnable = new MyRunnable();
// 4.把此对象作为参数放入 Thread 类的构造器,从而创建 Thread 类的对象
Thread thread = new Thread(myRunnable);
// 5.调用 Thread 类的对象的 start() 方法
thread.start();
}
}
相比于继承 Thread
类的方式,用实现 Runnable
接口的方式更好,原因有二:
- Java 是单继承多实现,继承了
Thread
就不能继承别的类了(那岂不是所有自己写的子类都没法用于多线程了吗?),但实现了Runnable
却可以同时实现别的接口。 - 用一个
Runnable
实现类的对象就可以创建多个线程(这些线程共享这个对象),因而可以很方便地让多个线程共享数据(各个线程共享同一个Runnable
类型的对象)。
第三种:实现Callable接口
// 1.创建一个实现 Callable 接口的类
class MyCallable implements Callable<Integer> {
// 2.实现 Callable 的抽象 call() 方法
@Override
public Integer call() throws Exception { // Callable 的 call() 方法可以抛出异常
int sum = 0;
for (int i = 0; i < 100; i++) sum += i;
return sum; // Callable 的 call() 方法可以有返回值
}
}
public class Test {
public static void main(String[] args) {
// 3.创建实现了 Callable 的类的对象
MyCallable myCallable = new MyCallable();
// 4.把此对象作为参数放入 FutureTask 类的构造器,从而创建 FutureTask 类的对象
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
// 5.把 FutureTask 类的对象作为参数放入 Thread 类的构造器,从而创建 Thread 类的对象
Thread thread = new Thread(futureTask);
// 6.调用 Thread 类的对象的 start() 方法
thread.start();
// 7.调用 FutureTask 类的对象的 get() 方法,以获取线程的返回值(如果不需要返回值则不需要这一步)
try {
Object sumObject = futureTask.get(); // 获取线程的返回值
System.out.println(sumObject);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
Callable
与 Runnable
的主要区别:
Callable
的call()
方法可以有返回值。Callable
的call()
方法可以抛异常。Callable
支持泛型。
第四种:使用线程池
// 1.创建一个实现 Runnable 接口的类
class MyRunnable implements Runnable {
// 2.实现 Runnable 的抽象 run() 方法
@Override
public void run() {
for (int i = 0; i < 100; i++) System.out.println(i);
}
}
public class Test {
public static void main(String[] args) {
// 3.创建 ExecutorService 类的对象(创建线程池)
ExecutorService executorService = Executors.newFixedThreadPool(10); // 固定线程数量的线程池
// 4.创建实现了 Runnable 的类的对象
MyRunnable myRunnable = new MyRunnable();
// 5.把实现了 Runnable 的类的对象作为参数放入 ExecutorService 类的对象的 execute() 方法
executorService.execute(myRunnable); // Runnable 用 execute() 方法,Callable 用 submit() 方法
// 6.关闭线程池
executorService.shutdown();
}
}
终极结论
线程的创建看起来有四种方式: 继承 Thread
类、实现 Runnable
接口、实现 Callable
接口、使用线程池。
但是!!!
本质上,线程的创建方式其实只有一种:
new 一个 Thread 对象,并给它一个 Runnable 类型的对象
Thread 对象负责启动线程,Runnable 对象负责执行线程
至于具体的原因,涉及到源码,想了解的话可以看这篇文章中的详细分析:彻底理解Java线程的创建 深入源码 生动分析