创建多线程的三种方式

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接口:

java代码插件调用线程增多 java添加线程_java

3. 实现了Callable接口

上面的两种方法,我们都是通过覆写run方法来书写自己的代码,可是这样会有个问题,首先如果我们希望自己的方法可以返回一些值,这是无法做到的:

java代码插件调用线程增多 java添加线程_多线程_02


其次我们想要在方法上抛出一些异常也是不可以的:


java代码插件调用线程增多 java添加线程_java_03

基于上面两点,JDK1.5之后出现了第三种创建线程的方式,实现Callable接口,实现了Callable接口有两点好处:

  • 可以有返回值
  • 能抛出异常

当然也有一些缺点,就是线程的创建有点麻烦。

我们看一下Callable的源代码:

java代码插件调用线程增多 java添加线程_java代码插件调用线程增多_04

我们看到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。

这些类之间的关系:

java代码插件调用线程增多 java添加线程_java代码插件调用线程增多_05