写在前面

如果觉得有所收获,记得的点个关注和点个赞,感谢支持。
原始的创建线程的方法有三种,分别是:

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口

这篇博文就单纯的针对这三种接口,讲解如何使用,旨在使用,并不深入讲解各种原理乃至讲解线程相关,嘿嘿嘿。

继承 Thread 类

这是一种最为原始的方式,继承 Thread 类,重写 run 方法以实现线程功能。

1
2
3
4
5
6
public class HelloThread extends Thread {
@Override
public void run(){
System.out.println("hello.");
}
}

上面的代码是指,我创建了一个名为 HelloThread 的类,该类由于继承 Thread 类因而是一个线程类,它重写了父类的 run 方法,打印了一句话:【hello.】。使用时也异常简单:

1
2
Thread thread = new HelloThread();
thread.start();

首先实例化一个线程出来,然后启动线程,当线程启动之后,控制台上打印了一句话:【hello.】。一般情况下不要使用这种方式来创建线程,因为通过继承来实现实在是太臃肿了,很多场景下我们只需要让线程跑起来,实现某个功能(即重写 run 方法),但是继承会实现 Thread 类的全部信息,性能消耗太大。而且 Java 是单继承的,继承了 Thread 类就不能继承其他类了。

实现 Runnable 接口

Runnable 接口是一切线程创建的根源,其实上面【继承 Thread 类】的途径,也是间接使用了本途径来创建线程的。比较传统的实现 Runnable 接口的方式是,创建一个类,该类 implements Runnable 来实现 Runnable 接口。

1
2
3
public class HelloRunnable implements Runnable {
// ...
}

但是创建一个类,未免太大张旗鼓了些,还要新建一个类,设置好类名,实现接口,之后再实例化,兴师动众。其实实例化对象并不需要创建一个类出来,实现接口就行,用匿名内部类。

1
2
3
Runnable helloRunnable = new Runnable() {
// ...
};

此外,Runnable 接口是一个函数式接口,只定义了 run 方法,可以使用 lambda 表达式的方式来实例化,那就更简单了。

1
2
3
Runnable HelloRunnable = () -> {
// ...
};

上面三种实现,都只是写了外壳,里面没有写具体的实现过程,具体的实现是要重写 Runnable 接口的 run 方法的。我写了三种实现 Runnable 接口的代码,第一种最容易懂,后面两种如果有困惑,看一看 lambda 表达式就能理解了。实现了 Runnable 接口之后,把它作为参数,放进 Thread 的构造方法里就可以了。

1
2
Thread thread = new Thread(runnable);
thread.start();

这样就可以了。要不再完整地走一遍?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 实现 Runnable 接口,重写 run 方法 (这里使用匿名内部类的方式,即上面的第二种)
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("by runnable");
}
};

// 创建线程并开启
Thread thread = new Thread(runnable);
thread.start();

// 控制台上会打印出这样一句话:by runnable

实现 Callable 接口

以上两种方式,都没有任何的返回值,线程执行动作,执行完就结束了,无声无息。实现 Callable 接口的目的,就是为了让线程执行完之后,能返回信息。简单对比一下,Runnable 接口和 Callable 接口,在代码上的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 实现 Runnable 接口
Runnable runnable = new Runnable() {
@Override
public void run() {
// ...
}
};

// 实现 Callable 接口
Callable callable = new Callable() {
@Override
public Object call() throws Exception {
// ...
return null;
}
};

你会发现,实现两个接口都只需要重写一个方法:

  • 实现 Runnable 接口需要重写【没有返回值】的 run 方法
  • 实现 Callable 接口需要实现【返回一个对象】的 call 方法。

其他的地方,在用法上仿佛没有什么不同。实际上,Callable 接口还支持泛型,你可以指定返回值的数据类型:

1
2
3
4
5
6
7
8
// 指定返回 String 类型
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// ...
return null;
}
};

传统的线程设计,是没有返回值的概念的,因此没办法用线程类来获得返回值。JUC 包设计了一个新的接口:Future,来接收线程的返回值(和其他的功能)。Future 类是一个接口,无法直接实例化,因此又设计了一个名为 FutureTask 的类,该类实现了Future 接口和 Runnable 接口,打通了【线程功能】和【返回值功能】。

1
2
3
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);

上面这两行代码,是将刚才写好的 callable 对象,放进 futureTask 中,辗转放进线程中。你可以感受到,FutureTask 类是一个中介,它也支持泛型(不过上面这两行代码没写泛型)。FutureTask 类有一个 get 方法,用于获取 callable 的返回值。

1
2
Object returnStr = futureTask.get();

(如果指定了 FutureTask 的泛型,上面还可以更确切地指定数据类型,例如把上面代码的 Object 改成 String

这个 get 方法需要处理两类异常:InterruptedExceptionExecutionException