继承Thread类和实现Runnable接口法的异同

导语:在java中实现多线程的方法有两种:继承Thread类和实现Runnable接口。在使用这两种方法创建多线程类时有什么不同呢?下面作简要分析。

一、继承Thread类法

1.1. 这里先给出一个小demo,实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyThread extends Thread {
private String name;

public MyThread(String name) {
this.name = name;
}

@Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(Thread.currentThread().getName() + "+" + name + "+运行了+" + (i + 1) + "次");
}
}

public class ThreadDemo1 {
public static void main(String[] args) {
MyThread mt_A = new MyThread("线程A");
MyThread mt_B = new MyThread("线程B");
System.out.println("当前线程为:" + Thread.currentThread().getName());
mt_A.start();
mt_B.start();
}
}

运行结果(某次):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
当前线程为:main
Thread-1+线程B+运行了+1次
Thread-0+线程A+运行了+1次
Thread-1+线程B+运行了+2次
Thread-0+线程A+运行了+2次
Thread-1+线程B+运行了+3次
Thread-0+线程A+运行了+3次
Thread-1+线程B+运行了+4次
Thread-0+线程A+运行了+4次
Thread-1+线程B+运行了+5次
Thread-0+线程A+运行了+5次
Thread-1+线程B+运行了+6次
Thread-0+线程A+运行了+6次
Thread-1+线程B+运行了+7次
Thread-0+线程A+运行了+7次
Thread-1+线程B+运行了+8次
Thread-0+线程A+运行了+8次
Thread-1+线程B+运行了+9次
Thread-0+线程A+运行了+9次
Thread-1+线程B+运行了+10次
Thread-0+线程A+运行了+10次

从运行结果可以看出,开启了两个新线程Thread-0和Thread-1,两个线程互相抢占资源完成了打印操作,实现了多线程操作!


1.2. 为什么用start()启动线程而不是用run()方法?

因为线程的运行需要本地操作系统的支持,先分别来看看start()和run()方法的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*start()方法源码*/
public synchronized void start() {
if (threadStatus != 0) //如果当前线程不处于就绪状态,启动时就报错
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}

private native void start0();

在上面代码中出现了关键字native,表明了start0()是一个本地方法,说明start0()方法的运行需要本地操作系统的支持。

1
2
3
4
5
6
7
/*run()方法源码*/
@Override
public void run() {
if (target != null) {
target.run();
}
}

(1)从上面两处代码可以看出:

  • start()方法最后是由start0()方法完成,它会新运行一个线程,新线程会调用run()方法,且start()方法不能被重复调用[1],否则会抛出IllegalThreadStateException异常。
  • run()方法同一般成员方法一样,可以被重复调用。单独调用run()的时不会启动新线程,而是继续在当前线程中执行run()。为了验证run()方法不会启动新线程,可以将1.小demo中的mt_A.start();mt_B.start();改为mt_A.run();mt_B.runt();
    运行结果(每次相同):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    当前线程为:main
    main+线程A+运行了+1次
    main+线程A+运行了+2次
    main+线程A+运行了+3次
    main+线程A+运行了+4次
    main+线程A+运行了+5次
    main+线程A+运行了+6次
    main+线程A+运行了+7次
    main+线程A+运行了+8次
    main+线程A+运行了+9次
    main+线程A+运行了+10次
    main+线程B+运行了+1次
    main+线程B+运行了+2次
    main+线程B+运行了+3次
    main+线程B+运行了+4次
    main+线程B+运行了+5次
    main+线程B+运行了+6次
    main+线程B+运行了+7次
    main+线程B+运行了+8次
    main+线程B+运行了+9次
    main+线程B+运行了+10次

从执行结果可以看出,上述字符串的打印都是在主线程中完成的,未开启新线程,并且打印是按照先A后B的顺序执行的!

二、实现Runnable接口法

2.1. 同样这里给出此方法实现的小demo,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyRunnable implements Runnable {
private int ticket = 10;

@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "+sell ticket:" + ticket--);
}
}
}
}

public class ThreadDemo2 {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
new Thread(my).start();
new Thread(my).start();
new Thread(my).start();
}
}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
Thread-1+sell ticket:10
Thread-2+sell ticket:8
Thread-0+sell ticket:9
Thread-2+sell ticket:6
Thread-1+sell ticket:7
Thread-2+sell ticket:4
Thread-2+sell ticket:2
Thread-2+sell ticket:1
Thread-0+sell ticket:5
Thread-1+sell ticket:3

由结果可知使用Runnable接口的方式,完成了买票的业务。即用3个线程一块进行卖票,每张票只卖了一次,符合实际情况。

2.2. 用继承Thread类的方法执行卖业务时,就会发现行不通,具体情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyRunnable extends Thread {
private int ticket = 10;
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "+sell ticket:" + ticket--);
}
}
}
}

public class ThreadDemo2 {
public static void main(String[] args) {
new MyRunnable().start();
new MyRunnable().start();
new MyRunnable().start();
}
}

运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Thread-0+sell ticket:10
Thread-1+sell ticket:10
Thread-1+sell ticket:9
Thread-0+sell ticket:9
Thread-1+sell ticket:8
Thread-2+sell ticket:10
Thread-1+sell ticket:7
Thread-0+sell ticket:8
Thread-1+sell ticket:6
Thread-2+sell ticket:9
Thread-1+sell ticket:5
Thread-0+sell ticket:7
Thread-1+sell ticket:4
Thread-1+sell ticket:3
Thread-1+sell ticket:2
Thread-1+sell ticket:1
Thread-2+sell ticket:8
Thread-0+sell ticket:6
Thread-2+sell ticket:7
Thread-0+sell ticket:5
Thread-2+sell ticket:6
Thread-0+sell ticket:4
Thread-2+sell ticket:5
Thread-0+sell ticket:3
Thread-2+sell ticket:4
Thread-0+sell ticket:2
Thread-2+sell ticket:3
Thread-0+sell ticket:1
Thread-2+sell ticket:2
Thread-2+sell ticket:1

由运行结果可知,此处的三个线程各卖了10张票,与实际情况不符。

三、小结

Thread类也实现了Runnable接口,应尽量使用实现Runnable的方式,因为实现Runnable接口的方式比继承Thread类方法多以下优势:
① 适合多个相同程序代码的线程去处理同一资源;
② 避免了Java单继承带来的局限性;
③ 增强了程序的健壮性,代码能被多个线程共享,代码与数据是独立的。

[1]:skywang12345. Java多线程系列–“基础篇”03之 Thread中start()和run()的区别

码哥 wechat
欢迎关注个人订阅号:「码上行动GO」