Java中常见的线程创建实现方式有三种:继承Tread类、实现runnable接口、调用Callable和Future。接下来,以抢票线程为例,分析3种实现办法,并对比3种方法的不同点。
1 继承Thread类,创建线程类
//继承Thread类创建线程
//第一步:新建类,继承Tread
public class ThreadInitial extends Thread{
private int ticket = 3;//票数
//调用Tread的构造函数,修改线程名字,便于理解
public ThreadInitial(String str) {
super(str);
}
//第二步:将需要执行的代码写到run()
public void run() {
for (int i = 0; i < 10; i++)
if(ticket > 0)
System.out.println(this.getName() + ": ticket" + ticket--);//getName()获取当前线程的名字
}
public static void main(String[] args) {
System.out.println("Main thread: " + Thread.currentThread().getName());
//第三步:执行start()
//第一个线程
new ThreadInitial("抢票线程1").start();
//第二个线程
new ThreadInitial("抢票线程2").start();
}
}
/*Output
//因为CPU轮换执行原因,每次结果不同
Main thread: main
抢票线程2: ticket3
抢票线程1: ticket3
抢票线程2: ticket2
抢票线程1: ticket2
抢票线程2: ticket1
抢票线程1: ticket1
*/
如上图,该方法的线程创建主要是3步:
Step1:新建类,继承Thread
Step2:实现该类的run()方法,将需要在线程中运行的代码写于其中
Step3:创建实例,并执行start()方法
使用该方法创建的线程,多个线程之间无法共用线程例的实例变量。从打印信息我们可以理解:相当于两个票务员(线程)都获取了相同工作任务(抢票次数count)。两个票务员工作任务相同,但不是同一件工作。所以各自分别抢票3次。
2 实现runnable()接口
//实现Runnable()创建线程
//第一步:新建类,实现Runnable
public class ThreadInitial implements Runnable{
private int ticket = 3;//抢票次数,线程实程,多个线程共用
//第二步:将需要执行的代码写到run()
public void run() {
for (int i = 0; i < 10; i++)
if (ticket > 0)
System.out.println(Thread.currentThread().getName() + ": ticket" + ticket--);
}
public static void main(String[] args) {
System.out.println("Main thread: " + Thread.currentThread().getName());
//第三步:创建实例
ThreadInitial threadInitial = new ThreadInitial();
//第四步:创建线程,执行start()
//第一个线程
new Thread(threadInitial,"抢票线程1").start();
//第二个线程
new Thread(threadInitial,"抢票线程2").start();
}
}
/*Output
Main thread: main
抢票线程2: ticket3
抢票线程1: ticket2
抢票线程2: ticket1
*/
该方法的步骤如下,可以实现资源共享。可以理解为:分配两个票务员一件事件,这件事情由两个票务员合作完成。
Step1:创建implements Runnable()的类
Step2:实现该类中的run()方法,写入要执行的代码
Step3:创建该类实例
Step4:利用Thread(Runnable,String name)创建线程,并执行start()
该方法可以实现多线程资源共享。对比方法1的代码及打印信息、方法2的代码及打印信息发现:ticket是共享资源。方法1种没有实现共享,而是分别执行,共打印6条票务信息;方法2中两个线程共享ticket,只打印3条票务信息。原因可以Thread(Runnable target, String name)解读。源码如下:
//Thread(Runnable target, String name)
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
//Thread类中的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
此时发现,方法2中,两个线程的target是一样的,所以会执行同一个target.run().
3 利用Callable、FutureTask创建线程
//利用Callable、FutureTask创建线程
//第一步:定义实现Callable接口的类
public class ThreadInitial implements Callable<Integer> {
//第二步:重写call()方法,即为线程执行体
int ticket = 3;//票务数
public Integer call() throws Exception {
for (int i = 0; i < 10; i++)
if(ticket > 0)
System.out.println(Thread.currentThread().getName() + ": ticket" + ticket--);
return ticket;
}
public static void main(String[] args) {
//第三步:获取实现callable接口的类的实例
ThreadInitial threadInitial = new ThreadInitial();
//第四步:使用FutureTask类包装实现的类
// 该FutureTask对象封装了该Callable对象的call()方法的返回值
FutureTask<Integer> threadSeed = new FutureTask<>(threadInitial);//封装
System.out.println("Main thread: " + Thread.currentThread().getId());
//第五步:创建线程,并捕获异常
new Thread(threadSeed,"抢票线程1").start();
new Thread(threadSeed,"抢票线程2").start();
//可以使用try catch捕获异常
//可以使用threadSeed.get()获取线程执行体Call()的返回值
}
}
/*OutPut:
Main thread: 1
抢票线程1: ticket3
抢票线程1: ticket2
抢票线程1: ticket1
*/
该方法的主要步骤如下,另外注意下打印信息,抢票线程2并没有执行,具体原因我还没有细致分析(抱歉,以后有时间再分析):
Step1:创建新类,implements Callable接口
Step2:重写Call()方法,该方法可返回值,可扔出异常
Step3:利用新创建类的实例
Step4:利用FutureTask封装该实例,获取FutureTask对象,该对象实现了Runnable接口
Step5:利用Tread(Runnable target)创建线程,并启动start()
4 主要区别
法1:extends Thread类实现创建线程; 法2: implements Runnable类创建线程; 法3:implement Callable+FutureTask封装
第1:法1 每次创建不同的target,无法进行线程间资源共享;法2与法3可以实现线程间资源共享,因其每次的target相同;
第2:法2与法3实现接口后,还可以继承其他类,程序具备一定可拓展性;
第3:法2与法1的执行体run()没有返回值,且不能捕获异常。法3可以做到。
第4:从代码上看出,复杂度上 法3 > 法2 > 法1; 功能上 法3 > 法2 > 法1