方式一:继承Thread类创建线程
在举栗子之前,我们先定义一个Account实体类:
1 | public class Account { |
使用继承Thread类的方法创建线程:
1 | public class CreateThreadByExtend extends Thread { |
运行结果:
1 | thread对象已经创建,但是还没有start时,线程的状态:Thread-0:NEW |
需要注意以下几个问题:
1.变量可以通过什么方式传递给线程?
可以通过setter方法,或者带参构造函数。
2.线程的状态?
在new Thread,但未start之前,线程的状态是NEW。在start线程后,线程的状态是RUNNABLE。在线程运行完成后,线程的状态是TERMINATED。
3.使用继承Thread的方式创建线程有什么优点、缺点?
* 优点:可以使用Thread类已经有的方法,可以使用this去调用
* 缺点:Java的单继承机制使得只能继承Thread一个类
方式二:使用Runnable创建线程
1 | public class CreateThreadByImplement implements Runnable { |
实现Runnable接口的类需要借助Thread类来启动线程,因为Runnable接口中没有start()方法。
不能使用this关键字了,需要使用Thread的静态方法来获取线程信息等。
使用Runnable接口实现可以继承其他类且可以继承多个接口,缺点是在run方法中不可以用this直接使用Thread的方法,需要使用Thread.currentThread先获取到线程对象
方式三:使用Callable创建线程
1 | public class CreateThreadByCallable implements Callable<Account> { |
Callable接收一个泛型参数,这个参数影响call方法的返回值以及FutureTask的类型值。
使用Callable接口创建线程,
- 优点:线程可以有返回值
- 缺点:需要使用FutureTask封装一层,再传递给Thread
对比:执行run方法的本质
我们来看一下继承Thread类的run方法是怎么实现的?
1 | /* What will be run. */ |
Thread类的run方法实际上是调用Runnable接口的run方法。但如果是继承Thread类来实现线程,那就重写了Thread类的run方法,启动线程实际上执行的是重写后的代码。
1 | class MyThread extend Thread { |
验证1:到底执行谁的run方法?
我们采用传入一个Runnable对象的方式创建一个线程。既实现Runnable接口的run方法,又重写Thread类的run方法,那么线程启动到底执行哪个run方法呢?
1 | // 使用匿名内部类的方式创建Runnable对象,如果使用Lambda表达式,不能在Lambda表达式中重写run方法 |
执行结果:
1 | 我是重写Thread类的run方法 |
从执行结果可知,我们重写的run方法覆盖了Thread类的run方法。也就是覆盖了Thread类的以下代码,使得Thread不再调用Runnable对象的run方法
1 |
|
面试1:一句话总结线程的创建方式
准确地讲,创建线程只有一种方式,那就是构造Thread类。而实现线程的执行单元有两种方式:
- 实现Runnable接口的run方法,并把Runnable实例传递给Thread类
- 继承Thread类,重写Thread类的run方法
面试2:继承Thread类和实现Runnable接口的方法有什么优缺点?
继承Thread类
优点:
- 可以直接使用this关键字调用Thread类当中的方法
缺点:
- Java中不支持多继承,缺少灵活性、扩展性
- 新建线程的损耗多,一个Thrad类就只能使用一次
实现Runnable接口
优点:
- 灵活性、扩展性好
- 新建线程的损耗小,实现了Runnable的对象可以被多个Thread反复使用
缺点:
- 不能使用this关键字,需要使用Thread的静态方法