初识Java多线程之创建多线程的几种方式(一)

前言

个人认为多线程属于java的中高级知识,是java当中的重点。也是面试中的几大难点之一。作为初学者往往搞不明白多线程。我个人对多线程也没有太深入的认识,所以这几天也在看多线程相关的知识。在此记录下来以供学习参考。

创建多线程的两种方式

虽然标题是创建多线程的两种方式,但实际上创建多线程的方式不止两种。本篇文章暂时说两种简单常见的。其他创建方式将在后续文章补充。

本篇文章中的代码都不是线程安全的,仅供参考。

继承Thread类

我们只需要继承Thread类,然后实现其中的run方法即可。使用时只需new出Thread实现对象,然后调用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
package com.example.socket.multithreading;

/**
* 创建多线程方式一:继承Thread类
*/
public class ExtendThread extends Thread {

private static int num = 100;

@Override
public void run() {
while (num > 0) {
System.out.println(currentThread().getName() + "当前票号:" + num);
num--;
}
}

public static void main(String[] args) {
Thread extendThread1 = new ExtendThread();
Thread extendThread2 = new ExtendThread();
Thread extendThread3 = new ExtendThread();
// 注意要调用start方法而不是run方法
// 错误
// extendThread1.run();
// 正确
extendThread1.start();
extendThread2.start();
extendThread3.start();
}
}

实现Runnable接口

与继承Thread类基本一致,都是实现run方法,然后调用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
package com.example.socket.multithreading;

/**
* 创建多线程方式二:实现Runnable接口方式
*/
public class RunnableImpl implements Runnable {

private int num = 100;

@Override
public void run() {
while (num > 0) {
System.out.println(Thread.currentThread().getName() + "当前票号:" + num);
num--;
}
}

public static void main(String[] args) {
// 创建Runnable实现类
Runnable runnable = new RunnableImpl();
// 放入Thread的构造器中
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
}
}

两种创建多线程方式应该选用哪种?

个人倾向于选择实现Runnable的方式。因为Java只能单继承,一旦继承了Thread类就不能继承其他父类了。但是Java可以实现多个接口,所以实现Runnable接口不影响程序可扩展性。

透过现象看本质

我们都知道调用start启动一个线程之后,最终会执行该线程的run方法。在继承Thread类的时候,由于我们实现了run方法,所以启动线程,最终会调用子类当中的run方法。但是我们实现Runnable接口并没有显式或隐式的继承Thread,那么我们的run方法是怎么被调用的呢?
通过看Thread的源码可以发现,Thread也实现了Runnable接口,当中也有一个run方法。在我们start的时候调用的其实是Thread中的run方法。
以下是该方法的实现。

1
2
3
4
5
6
@Override
public void run() {
if (target != null) {
target.run();
}
}

我们发现在target不为null的情况下会调用target的run方法,那么这个target是谁呢?
这个target就是我们传入Thread构造器的Runnable的实现类

1
2
3
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}

所以虽然我们并没有显式或隐式的继承Thread但是最终调用的还是我们自己实现的run方法。

线程的生命周期

线程生命周期描述
线程生命周期图