我们常见的添加线程的方法通常是两种:
①继承Thread类,实现run方法,调用start()方法开启线程;
②实现Runnable接口,实现run方法, 调用start()方法开启线程;
其实还有第三种常用的添加线程的方式:
是通过Callable和Future创建线程
1. 继承Thread类添加线程
使用该方法添加线程的步骤是:
第一步:创建类继承Thread类
第二步:实现run方法,将任务写在run方法里面
第三步:在main方法new出该类,调用start方法开启线程
代码实现如下:
public class ThreadTest extends Thread {
//实现run方法
@Override
public void run() {
for(int i=0;i<=6;i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
try {
Thread.sleep(new Random().nextInt(100)); //休眠
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--finsh");
}
public static void main(String[] args) {
new ThreadTest("A").start();
new ThreadTest("B").start();
}
}
输出结果为(结果不唯一):
Thread0–1
Thread1–1
Thread0–2
Thread0–3
Thread1–2
Thread1–3
Thread0–4
Thread0–5
Thread0–6
Thread0–finish
Thread1–4
Thread1–5
Thread1–6
Thread1–finish
我们可以发现,继承Thread类创建的线程可以拥有自己独立的类成员变量i。
2.实现Runnable接口创建线程
使用该方法创建线程的步骤是:
第一步:创建类实现Runnable接口;
第二步:实现run方法,将任务写在run方法内;
第三步:创建Thread对象,创建Runnable实例,将其传入Thread对象中;
第四步:调用start方法开启线程。
代码实现如下:
public class RunableTest implements Runnable{
private int j;
//实现run方法
@Override
public void run() {
for(int i=0;i<=10;i++) {
try {
Thread.sleep(new Random().nextInt(100)); //休眠
j++;
System.out.println(Thread.currentThread().getName()+"--"+j);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name+" finsh");
}
public static void main(String[] args) {
RunableTest R=new RunableTest();
new Thread(R).start();
new Thread(R).start();
}
}
执行结果为:
Thread0–1
Thread1–2
Thread0–3
Thread0–4
Thread1–5
Thread1–6
Thread0–7
Thread0–8
Thread0–9
Thread0–10
在加入休眠操作之后,可以发现,虽然线程不同,但是i却是进程共享的。
3.通过Callable和Future创建线程
先简单了解以下Callable和Future接口以及他的实现类FutureTask:
- Callable是一个接口,它与Runnable极其相似,但在Runnable接口中run方法是线程执行体,而在Callable中call方法是线程执行体。
- call能够实现run能实现的所有功能,除此之外还多出以下两个功能:
- call方法允许有返回值,可以在执行完后返回数据;
- call方法能够声明抛出的异常;
- FutureTask类实现了Runnable和Future接口;
- Future接口是对Callable任务的执行结果进行取消,查询是否完成。
使用该方法创建线程的步骤是:
第一步:创建类实现Callable接口;
第二步:实现call方法, 将任务放在call方法内;
第三步:创建Callable实例,创建FutureTask实例,将Callable实例传入FutureTask中;
第四步:创建Thread对象,将FutureTask实例传入Thread对象中;
第五步:调用start方法, 开启线程。
代码实现如下:
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<Integer> {
//实现call方法
@Override
public Integer call() throws Exception {
int i=0;
for(;i<10;i++) {
System.out.println(Thread.currentThread().getName()+ "--" + i);
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("finsh");
return i;
}
public static void main(String[] args) {
CallableTest callable1 = new CallableTest();
CallableTest callable2 = new CallableTest();
FutureTask<Integer> furureTask1 = new FutureTask<Integer>(callable1);
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(callable2);
new Thread(Task1).start();
new Thread(Task2).start();
}
}
输出结果如下:
Thread0–1
Thread1–1
Thread0–2
Thread0–3
Thread1–2
Thread0–4
Thread0–5
Thread0–6
finish
Thread1–3
Thread1–4
Thread1–5
Thread1–6
finish
在该输出方法中,线程在执行过程中并不会输出返回值,如果要获得返回值则调用futureTask1.get()即可得到返回值。
4.区别:
三者区别在以上已经有所说明,总结来说有以下三点:
- 继承Thead类创建的线程可以拥有独立的成员变量,而实现Runnable接口创建的线程中的成员变量则是进程共享的;
- 三者的创建方式有所不同;
- 使用Callable和Task创建的线程的执行体与前两者的执行体不同,前两者是run方法作为执行体,后者是call方法作为执行体。call方法作为执行体有以下两个优势:1)call方法允许有返回值,可以在执行完后返回数据;
2) call方法能够声明抛出的异常。