Java中线程的创建方式有3种.下面依次介绍:
一.继承Thread类创建线程类
二.实现Runable接口创建线程类
三.使用Callable和Future创建线程类
从Java5开始,Java提供了Callable接口,该接口怎么看都像是Runable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。
(1) call( ) 方法可以有返回值;
(2) call( )方法可以声明抛出异常。
步骤如下:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并开启新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,调用 get()方法会阻塞线程。
代码如下:
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
}
return i;
}
});
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ " 的循环变量i的值:" + i);
if (i == 20) {
// 实质还是以Callable对象来创建、并启动线程
new Thread(task, "有返回值的线程").start();
}
}
try {
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
} catch (Exception ex) {
ex.printStackTrace();
}
第23行代码对应的打印结果为 子线程的返回值:200
。 这是因为第5-7行for循环执行完之后,才进入第8行代码。
上面程序中可以发现, 实现Callable接口与实现Runnable接口并没有太大的差别,只是Callable的call()方法允许声明抛出异常,而且允许带返回值。
上面程序中的第1-10行代码是以Callable对象来启动线程的关键代码。程序先创建一个Callable对象,然后将该实例包装成一个 FutureTask对象。主线程中当循环变量i等于20时,程序启动以FutureTask对象为target 的线程。程序最后调用FutureTask 对象的get()方法来返回call0方法的返回值一该方法将 导致主线程被阻塞,直到call()方法结束并返回为止。
运行上面程序,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值。.
四. 创建线程的三种方式对比
通过继承Thread类或实现Runnable、Callable 接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现Runnable接口和实现Callable 接口归为一种方式。 这种方式与继承Thread方式之间的主要差别如下。
采用实现Runnable、Callable 接口的方式创建多线程的优缺点:
-
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
-
在这种方式下,多个线程可以共享同一个target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
-
劣势是,编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
采用继承Thread类的方式创建多线程的优缺点:
-
劣势是,因为线程类已经继承了Thread 类,所以不能再继承其他父类。
-
优势是,编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法, 直接使用this即可获得当前线程。
鉴于上面分析,因此一般推荐采用实现Runnable接口、Callable 接口的方式来创建多线程。