创建多线程的三种方式
继承Thread类
通过继承Thread类来创建并启动多线程的步骤为:
- 定义Thread类的子类,并继承重写该类的run()方法,该run()方法就代表该线程要完成的任务。
- 创建Thread子类的实例
- 调用该实例对象的start()方法启动该线程
实现Runnable接口创建线程类
步骤如下:
- 定义Runnable接口的实现类,重写该接口的run()方法
- 创建Runnable接口的实例,并用该实例对象作为Thread的target来创建Thread对象,该Thread才是真正的线程对象
- 调用线程对象的start方法启动该线程
例子(SecondThread实现了Runnable接口):
1 |
|
使用Callable和Future创建线程
Callable接口提供了call()方法来作为线程执行体,但是call方法比run方法功能更强大
- call方法可以有返回值
- call方法可以声明抛出异常
Java提供了Future接口来代表Callable接口call方法的返回值,并且为Future提供了一个FutureTask实现类,该实现类实现了Future接口,并且实现了Runnable接口,可以作为Thread类的target。
在Future接口定义了几个公共方法控制与它关联的Callable任务
boolean cancel(boolean mayInterruptRunning)
功能:试图取消该Future关联的Callable任务
参数:
- mayInterruptRunning
返回值:一个布尔值
V get()
功能:返回Callable任务中的返回值,调用该任务,将导致程序阻塞,必须等到子线程结束才会得到返回值
参数:无
返回值:指定返回值
V get(long timeout, TimeUnit unit)
功能:返回Callable任务里call任务的返回值,该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间仍然没有返回值,将抛出TimeoutException异常
boolean isCanceled()
功能:如果该Callable任务在正常完成前被取消,则返回true
boolean isDone()
功能:如果该任务已经完成,返回true
Callable接口有泛型限制,Callable接口里的泛型形参类型和call方法返回值相同,而且Callable接口为函数式接口,可以直接使用Lambda表达式创建Callable对象。
步骤为:
- 创建Callable接口的实现类,并实现call方法,创建实例对象
- 使用FutureTask来包装该实现类,该FutureTask对象封装了该Callable对象的call方法
- 使用FutureTask对象作为Thread对象的target创建线程对象,并启动线程
- 调用FutureTask对象的get方法来获得返回值
三种创建对象的方式对比
实现Runnable接口和实现Callable接口的方式基本差不多,只是Callable接口里可以有返回值,可以声明抛出异常。因此可以将两种实现接口的方法归为一种方式,和继承Thread类的主要区别有:
- 线程类只是实现了接口,还可以继承其他类
- 在这种方式下,多个线程可以共享一个target对象,所以非常适合多个线程处理一份资源的情况,从而可以将CPU,代码和数据分开,形成清晰模型
- 劣势是,编程稍微复杂,如果需要访问当前线程,则必须使用Thread.currentThread方法
采用继承Thread类的方法创建多线程优缺点为:
- 劣势是,因为线程类已经继承了Thread,不能再继承其他类
- 优势是,编程简单,如果要访问当前线程,直接使用this
综上,一般推荐采用Runnable接口、Callable接口来创建多线程。