创建多线程的三种方式
1. 继承Thread类
继承Thread类,覆写run方法。
使用setName()和getName()来设置和获取线程的名字。
/**
* 以火车票为例
*/
public class BuyTicketThreadExtend extends Thread {
// 总票数,为了使多个实例抢的都是这10张票,所以用static修饰
static int ticketNumber = 10;
// 设置线程名字的方法
public BuyTicketThreadExtend(String name) {
setName(name);
}
/**
* 覆写run方法
*/
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticketNumber > 0) {
System.out.println("我在" + getName() + "买到了,座号为:\t" + ticketNumber--);
}
}
}
}
实例化线程并启动线程:
public class BuyTicketThreadExtendTest {
public static void main(String[] args) {
BuyTicketThreadExtendt1 = new BuyTicketThreadExtend("one");
t1.start();
BuyTicketThreadExtendt2 = new BuyTicketThreadExtend("two");
t2.start();
}
}
结果:
我在one买到了,座号为: 10
我在two买到了,座号为: 10
我在two买到了,座号为: 9
我在two买到了,座号为: 8
我在two买到了,座号为: 7
我在two买到了,座号为: 6
我在two买到了,座号为: 5
我在two买到了,座号为: 4
我在two买到了,座号为: 3
我在two买到了,座号为: 2
我在two买到了,座号为: 1
2. 实现Runnable接口
实现Runnable接口,覆写run方法:
/**
* 实现了这个接口,才会变成一个线程类。
*/
public class BuyTicketThreadImplements implements Runnable {
// 火车票张数,我们没有加上static修饰符
int ticketNumber = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticketNumber > 0) {
System.out.println("我在" + Thread.currentThread().getName() + "买到了票,座号为" + ticketNumber--);
}
}
}
}
实例化线程类并将其作为参数传递给Thread构造方法:
public class BuyTicketThreadImplementsTest {
public static void main(String[] args) {
BuyTicketThreadImplements tti = new BuyTicketThreadImplements();
Thread t1 = new Thread(tti, "窗口1");
Thread t2 = new Thread(tti, "窗口2");
t1.start();
t2.start();
}
}
结果:
我在窗口1买到了票,座号为10
我在窗口2买到了票,座号为9
我在窗口2买到了票,座号为8
我在窗口2买到了票,座号为7
我在窗口2买到了票,座号为6
我在窗口2买到了票,座号为4
我在窗口2买到了票,座号为3
我在窗口2买到了票,座号为2
我在窗口2买到了票,座号为1
我在窗口1买到了票,座号为5
思考:现在已经知道两种方式,到底哪一种方式更好呢?
- 在Java中是单继承的,所以我们继承了Thread类就没法继承其他的类,所以我们更多的使用的是实现Runnable接口。
- 实现了Runnable的方式共享资源的能力的更强,不需要加一个static来修饰。适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离,很好地体现了面向对象的设计思想。
所以在大部分的应用程序中都会采用第二种方式来创建多线程,即实现Runnable接口。
我们通过类的继承关系也可以看到,Thread本质上也是去实现了Runnable接口:
3. 实现了Callable接口
上面的两种方法,我们都是通过覆写run方法来书写自己的代码,可是这样会有个问题,首先如果我们希望自己的方法可以返回一些值,这是无法做到的:其次我们想要在方法上抛出一些异常也是不可以的:
基于上面两点,JDK1.5之后出现了第三种创建线程的方式,实现Callable接口,实现了Callable接口有两点好处:
- 可以有返回值
- 能抛出异常
当然也有一些缺点,就是线程的创建有点麻烦。
我们看一下Callable的源代码:我们看到Callable是泛型接口。
如果我们在继承的时候写了类型,call方法的返回值就是指定类型的,而且可以抛出异常了:
public class CreateNewThread3 implements Callable<Integer> { @Override
public Integer call() throws Exception {
return null;
}
}
我们创建一个实现了Callable接口的类:
/**
* 返回一个10以内的随机数
*/
public class CreateNewThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);
}
}
我们怎么样来实例化并启动类:
@Test
public void testCreateNewThread3() throws ExecutionException, InterruptedException {
// 创建我们刚才写的类的实例
CreateNewThread3 newThread3 = new CreateNewThread3();
// 将该实例传递给FutureTask的构造方法,得到futureTask对象
FutureTask futureTask = new FutureTask<>(newThread3);
// 将futureTask实例传递给Thread的构造方法,得到Thread的对象
Thread thread = new Thread(futureTask);
// start实例
thread.start();
// 利用futureTask的get方法来获取返回值
Object o = futureTask.get();
System.out.println(o);
}
我们可以看到非常的麻烦,需要借助一个中间类FutureTask。
这些类之间的关系: