本文总结自《疯狂Java讲义》
一、继承Thread类
- 定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务。因此把 run() 方法称为线程执行体。
- 创建 Thread 子类的实例,即创建了线程对象。
- 调用线程对象的 start() 方法来启动该线程。
示例代码如下:
public class ThreadTest extends Thread{
private int x;
public void run(){
for (; x < 10; x++) {
System.out.println(getName() + " " + x); //获取线程名字
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ " " + i); //获取当前线程的名字
if(i == 5){
new ThreadTest().start();
new ThreadTest().start();
}
}
}
}
输出结果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-1 0
Thread-0 4
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
我们可以看到,main函数是最先执行完的,因为开辟一个线程后,该线程就会进入就绪状态,并不会直接进入运行状态,状态的切换由底层平台控制的,有一定的随机性。
如果main函数的循环次数再大一些,线程0和线程1就能在main函数执行完前开始执行。
二、实现Runnable接口
- 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
- 调用线程对象的 start() 方法来启动线程
示例代码如下:
public class ThreadTest implements Runnable{
private int x;
public void run(){
for (; x < 10; x++) {
System.out.println(Thread.currentThread().getName() + " " + x);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ " " + i);
if(i == 0){
new Thread(new ThreadTest()).start();
new Thread(new ThreadTest()).start();
}
}
}
}
输出结果:
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
Thread-0 0
Thread-1 0
Thread-1 1
Thread-1 2
Thread-0 1
Thread-1 3
Thread-0 2
Thread-0 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-1 4
Thread-1 5
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
Thread-0 7
Thread-0 8
Thread-0 9
区别主要在于,线程的创建是将实现了 Runnable 的类的实例作为参数,通过 new Thread(new threadTest())
方法来创建,再调用线程的 start() 方法执行线程。
三、使用Callable和Future
- 创建 Callable 接口的实现类 ,并实现 call() 方法,该 call() 方法将作为线程执行体,且该 call() 方法有返回值,再创建 Callable 实现类的实例。
- 使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
示例代码如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值:" + i);
}
return i;
}
});
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
if (i == 5){
new Thread(task, "有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
输出结果:
main的循环变量i的值:0
main的循环变量i的值:1
main的循环变量i的值:2
main的循环变量i的值:3
main的循环变量i的值:4
main的循环变量i的值:5
main的循环变量i的值:6
main的循环变量i的值:7
main的循环变量i的值:8
main的循环变量i的值:9
有返回值的线程 的循环变量i的值:0
有返回值的线程 的循环变量i的值:1
有返回值的线程 的循环变量i的值:2
有返回值的线程 的循环变量i的值:3
有返回值的线程 的循环变量i的值:4
有返回值的线程 的循环变量i的值:5
有返回值的线程 的循环变量i的值:6
有返回值的线程 的循环变量i的值:7
有返回值的线程 的循环变量i的值:8
有返回值的线程 的循环变量i的值:9
子线程的返回值:10
Callable 接口看起来就像是 Runnable 接口的增强版,call() 作为线程执行体,可以有返回值,还可以抛出异常。
Callable 接口没有实现 Runnable 接口,不能作为 Thread 的 target ,无法直接创建线程对象。
Callable 接口有泛型限制,返回值类型要与泛型类型一致。
Future 接口代表 Callable 接口里 call() 方法的返回值,并提供了一个实现类 FutureTask ,该实现类也实现了 Runnable 接口,因此可以将 FutureTask 与 Callable 关联起来,实现多线程。
三种方法的对比
- 继承 Thread 的方法已经继承了 Thread 类,无法再继承别的类,而 Runnable 和 Callable 可以继承别的类。
- Runnable 和 Callable 可以多个线程共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、数据分开,形成清晰的模型,较好地体现了面向对象的思想。
- Callable 类的线程可以经过 Future 包装后获取返回值。