线程入门
十月的第四周,学习多线程。
借着工作需求入门了线程,但是需求并不用考虑并发风险,使用的也是封装好的对象,因此这次入门入得比较浅。
这篇博文偏实用,有关概念解释的部分就略过不提了。
原始的创建线程的方法有三种,分别是:
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口
注意,这里说的是创建线程,因此只总结怎么【搭架子】,不总结怎么【使用线程】。
这一部分基本全部参考博客《CS-Notes》对于 Java 并发的梳理,这个博客的内容非常优质(不只是 Java 多线程与并发的部分),十分推荐。
继承 Thread 类
这是一种最为原始的方式,继承 Thread 类,重写 run 方法以实现线程功能。
1 | public class PzThread extends Thread { |
上面的代码是指,我创建了一个名为 PzThread
的类,该类由于继承 Thread 类因而是一个线程类,它重写了父类的 run 方法,打印了一句话:【hello,pz.】。
使用时也异常简单:
1 | Thread thread = new PzThread(); |
首先实例化一个线程出来,然后启动线程,当线程启动之后,控制台上打印了一句话:【hello,pz.】。
一般情况下不要使用这种方式来创建线程,因为通过继承来实现实在是太臃肿了,很多场景下我们只需要让线程跑起来,实现某个功能(即重写 run 方法),但是继承会实现 Thread 类的全部信息,性能消耗太大。而且 Java 是单继承的,继承了 Thread 类就不能继承其他类了。
实现 Runnable 接口
Runnable 接口是一切线程创建的根源,其实上面【继承 Thread 类】的途径,也是间接使用了本途径来创建线程的。
比较传统的实现 Runnable 接口的方式是,创建一个类,该类 implements Runnable
来实现 Runnable 接口。
1 | public class PzRunnable implements Runnable { |
但是创建一个类,未免太大张旗鼓了些,还要新建一个类,设置好类名,实现接口,之后再实例化,兴师动众。其实实例化对象并不需要创建一个类出来,实现接口就行,用匿名内部类。
1 | Runnable pzRunnable = new Runnable() { |
此外,Runnable 接口是一个函数式接口,只定义了 run 方法,可以使用 lambda 表达式的方式来实例化,那就更简单了。
1 | Runnable pzRunnable = () -> { |
上面三种实现,都只是写了外壳,里面没有写具体的实现过程,具体的实现是要重写 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。
需求一不小心写完了……啥都没学到,算了先写到这里叭。