并发编程 Thread

本文将介绍 Java 中的 Thread,它代表线程。

一、Thread 类

1. 说明

Java 用 Thread 类代表线程,所有的线程对象都必须是 Thread 类或其子类的实例。

2. 构造方法

1
Thread Thread(Runnable target, String name)
  • target:线程的任务;Runnable 实例

  • name:线程的名字

3. 类方法

1
Thread.currentThread()

获取当前的线程。

二、线程创建

1. 法 1:继承 Thread 类

  • 定义 Thread 的子类,重写 run() 方法

  • 创建该类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义 Thread 的子类,重写 run() 方法
class 类名 extends Thread {
@Override
public void run() {
···
}
}
}

// 创建该类的对象
类名 对象名 = new 类名();

// 调用对象的 `start()` 方法,启动线程
对象名.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FirstThread extends Thread {
private int i;

@Override
public void run() {
for ( ; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
}

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

2. 法 2:实现 Runnable 接口

  • 定义类,实现 Runnable 接口,重写 run() 方法

  • 实例化接口实现类

  • 以接口实现类的对象作为 Thread 的参数创建 Thread 的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义类,实现 Runnable 接口,重写 run() 方法
class 类名 implements Runnable{
@Override
public void run() {
···
}
}
}

// 实例化接口实现类
类名 对象名1 = new 类名();

// 以接口实现类的对象作为 Thread 的参数创建 Thread 的对象
Thread 对象名2 = new Thread(对象名1);

// 调用对象的 `start()` 方法,启动线程
对象名2.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SecondThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}

public class Test {
public static void main(String[] args) {
SecondThread secondThread = new SecondThread();
new Thread(secondThread).start();
new Thread(secondThread).start();
}
}

3. 优缺点

  • 继承 Thread 类方式访问当前类更简单,因为当前类就是它本身

    实现 Runnable 接口方式访问当前类需要借助 Thread.currentThread()

  • 继承 Thread 类方式无法继承其它类,因为它已经继承了 Thread 类

    实现 Runnable 接口方式可以继承其它类

  • 继承 Thread 类方式每创建一个线程时,都是创建了一个新的实例,因此只能共享类成员

    实现 Runnable 接口方式可以在创建线程时传入同一个对象,因此可以共享实例成员

  • 一般采用实现 Runnable 接口方式

三、线程控制

1. 线程启动

通过调用线程实例的 start() 方法启动线程。

需要注意的是:

  • 调用 start() 方法,JVM 会新建线程并调用其中的 run() 方法
  • 调用 run() 方法,只会在当前线程下简单执行该方法,并不会新建线程

2. 线程等待

Thread 提供了 join() 方法,它能够让一个线程等待另外一个线程完成后再继续进行。如果在线程中调用了另一个线程的 join() 方法,则线程将被堵塞,直至另一个线程执行完毕。

未使用 join() 方法:

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
>public class RelaxThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("休息了" + i + "下");
}
}
>}

>public class RunThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("走了" + i + "步");
if (i == 20) {
RelaxThread relaxThread = new RelaxThread();
relaxThread.start();
}
}
}
>}

>public static void main(String[] args) {
RunThread runThread = new RunThread();
runThread.start();
>}

使用 join() 方法:

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
>public class RelaxThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("休息了" + i + "下");
}
}
>}

>public class RunThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("走了" + i + "步");
if (i == 20) {
RelaxThread relaxThread = new RelaxThread();
relaxThread.start();
relaxThread.join();
}
}
}
>}

>public static void main(String[] args) {
RunThread runThread = new RunThread();
runThread.start();
>}

3. 线程休眠

1
Thread.sleep(time);

将当前线程设置为堵塞状态。

当线程被 “sleep” 之后,在其睡眠时间段内,它都不会获得执行的机会。

4. 线程让步

1
Thread.yield()

yield() 被调用后,线程便会让出 CPU 执行权,转为就绪状态,然后与其它线程一同争夺 CPU 执行权。

5. 线程中断

具体请看:

并发编程 终止线程

6. 设置为后台线程

有一种线程,它运行于后台,为其它线程提供服务,这种线程被称为“后台线程”。后台线程有一个特征:如果所有的前台线程死亡,后台线程也将自动死亡。

调用 Thread 的 setDaemon() 方法,该方法需要一个 Boolean 类型的参数,填入 true ,即可将指定的线程设置为后台线程。

需要注意的是,当前台线程死亡以后,后台线程并非立即死亡,其接收指令到做出响应也需要一定的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BackEndThread extends Thread {
@Override
public void run() {
while(true) {
System.out.println("后台");
}
}
}

public class TestThread extends Thread {
@Override
public void run() {
BackEndThread backEndThread = new BackEndThread();
backEndThread.setDaemon(true);
backEndThread.start();
for (int i = 0; i < 99; i++) {
System.out.println(getName() + " " + i);
}
}

public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
}

7. 设置线程优先级

每个线程都具有优先级,在争夺 CPU 的执行权时,优先级高的线程可以获得更多的执行机会。

每个线程线程默认的优先级与创建它的父线程的优先级相同。Thread 类提供了 setPriority(num)getPriority() 方法,用于设置和获取指定线程的优先级。

需要注意的是,优先级高的线程可以获得更多的执行机会,优先被执行,而不是一定先执行。

默认优先级 NORM_PRIORITY:5

最大优先级 MAX_PRIORITY:10

最小优先级 MIN_PRIORITY:1

参考

  • Java 并发编程实战