写在前面
如果觉得有所收获,记得的点个关注和点个赞,感谢支持。
原始的创建线程的方法有三种,分别是:
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口
这篇博文就单纯的针对这三种接口,讲解如何使用,旨在使用,并不深入讲解各种原理乃至讲解线程相关,嘿嘿嘿。
继承 Thread 类
这是一种最为原始的方式,继承 Thread 类,重写 run 方法以实现线程功能。
1 | public class HelloThread extends Thread { |
上面的代码是指,我创建了一个名为 HelloThread
的类,该类由于继承 Thread
类因而是一个线程类,它重写了父类的 run
方法,打印了一句话:【hello.
】。使用时也异常简单:
1 | Thread thread = new HelloThread(); |
首先实例化一个线程出来,然后启动线程,当线程启动之后,控制台上打印了一句话:【hello.
】。一般情况下不要使用这种方式来创建线程,因为通过继承来实现实在是太臃肿了,很多场景下我们只需要让线程跑起来,实现某个功能(即重写 run 方法),但是继承会实现 Thread 类的全部信息,性能消耗太大。而且 Java 是单继承的,继承了 Thread 类就不能继承其他类了。
实现 Runnable 接口
Runnable
接口是一切线程创建的根源,其实上面【继承 Thread
类】的途径,也是间接使用了本途径来创建线程的。比较传统的实现 Runnable
接口的方式是,创建一个类,该类 implements Runnable
来实现 Runnable
接口。
1 | public class HelloRunnable implements Runnable { |
但是创建一个类,未免太大张旗鼓了些,还要新建一个类,设置好类名,实现接口,之后再实例化,兴师动众。其实实例化对象并不需要创建一个类出来,实现接口就行,用匿名内部类。
1 | Runnable helloRunnable = new Runnable() { |
此外,Runnable 接口是一个函数式接口,只定义了 run 方法,可以使用 lambda 表达式的方式来实例化,那就更简单了。
1 | Runnable HelloRunnable = () -> { |
上面三种实现,都只是写了外壳,里面没有写具体的实现过程,具体的实现是要重写 Runnable
接口的 run
方法的。我写了三种实现 Runnable
接口的代码,第一种最容易懂,后面两种如果有困惑,看一看 lambda 表达式就能理解了。实现了 Runnable
接口之后,把它作为参数,放进 Thread 的构造方法里就可以了。
1 | Thread thread = new Thread(runnable); |
这样就可以了。要不再完整地走一遍?
1 | // 实现 Runnable 接口,重写 run 方法 (这里使用匿名内部类的方式,即上面的第二种) |
实现 Callable 接口
以上两种方式,都没有任何的返回值,线程执行动作,执行完就结束了,无声无息。实现 Callable
接口的目的,就是为了让线程执行完之后,能返回信息。简单对比一下,Runnable
接口和 Callable
接口,在代码上的区别:
1 | // 实现 Runnable 接口 |
你会发现,实现两个接口都只需要重写一个方法:
- 实现 Runnable 接口需要重写【没有返回值】的 run 方法
- 实现 Callable 接口需要实现【返回一个对象】的 call 方法。
其他的地方,在用法上仿佛没有什么不同。实际上,Callable 接口还支持泛型,你可以指定返回值的数据类型:
1 | // 指定返回 String 类型 |
传统的线程设计,是没有返回值的概念的,因此没办法用线程类来获得返回值。JUC 包设计了一个新的接口:Future
,来接收线程的返回值(和其他的功能)。Future
类是一个接口,无法直接实例化,因此又设计了一个名为 FutureTask
的类,该类实现了Future
接口和 Runnable
接口,打通了【线程功能】和【返回值功能】。
1 | FutureTask futureTask = new FutureTask(callable); |
上面这两行代码,是将刚才写好的 callable
对象,放进 futureTask
中,辗转放进线程中。你可以感受到,FutureTask
类是一个中介,它也支持泛型(不过上面这两行代码没写泛型)。FutureTask
类有一个 get
方法,用于获取 callable
的返回值。
1 | Object returnStr = futureTask.get(); |
(如果指定了 FutureTask
的泛型,上面还可以更确切地指定数据类型,例如把上面代码的 Object
改成 String
)
这个 get
方法需要处理两类异常:InterruptedException
和 ExecutionException
。