# 线程基础
# 一、基础概念
# 1、进程和线程
- 进程
进程是程序运行资源分配的最小单位。进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、 磁盘IO等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。
- 线程
线程是 CPU 调度的最小单位,必须依赖于进程而存在。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、 能独立运行的基本单位。
# 2、并行和并发
- 并行
指应用能够同时执行不同的任务,例如:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行。强调的是同时执行。
- 并发
指应用能够交替执行不同的任务,例如:单CPU核心下执行多线程并非是同时执行多个任务,而是计算机在以你觉察不到的速度来回切换执行多个任务而已。强调的是交替执行。
# 二、Java 中的线程
# 1、线程的启动和中止
# 启动
Java中启动线程的方式有两种:
- 继承
Thread
类。 - 实现
Runnable
接口。
public class NewThread {
/**
* 继承线程类
*/
private static class UseThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("I'm extended from Thread");
}
}
/**
* 实现 Runnable 接口
*/
private static class UseRunnable implements Runnable {
@Override
public void run() {
System.out.println("I implemented Runnable");
}
}
public static void main(String[] args) {
UseThread useThread = new UseThread();
useThread.start();
UseRunnable useRunnable = new UseRunnable();
new Thread(useRunnable).start();
}
}
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
31
32
33
执行结果:
I'm extended from Thread
I implemented Runnable
2
# Thread 和 Runnable 的区别
Thread
才是 Java 里对线程的唯一抽象,Runnable
只是对任务(业务逻辑) 的抽象。Thread
可以接受任意一个 Runnable
的实例并执行。
# 中止
其实一个线程想要中止,有3中情况。
线程自然中止
run()
方法执行完了或者是抛出了一个未处理的异常导致线程提前结束了。stop 调用
Thread
类的suspend()
、resume()
或者stop()
方法。但是这几个方式都是过时
的,也就是不建议使用的方法。因为调用这几个方法后,可能会造成一些资源(比如锁
)不会释放而造成死锁
的问题。中断 调用
Thread
类的interrupt()
方法对线程进行中断操作,实际上是改变了线程的中断标志位,然后在逻辑中进行处理看是否需要进行线程的中断。 这里关于中断有3个比较重要的方法:interrupt()
:调用这个方法表对线程进行中断操作,将中断标志改为true
。isInterrupted()
:获取中断标志位,判断当前线程是否被中断。Thread.interrupted()
:静态方法,也是获取中断标志位,判断当前线程是否被中断,但此方法会将中断标志位改为false
。
使用 isInterrupted()
结束线程的例子:
public class EndThread {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
while (!isInterrupted()) {
System.out.println(threadName + " is running");
System.out.println(threadName + "(inner)'s interrupt flag: " + isInterrupted());
}
System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("EndThread");
endThread.start();
Thread.sleep(20);
// interrupt 是中断线程,其实只是设置了线程的标识位
endThread.interrupt();
}
}
}
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
执行结果:
EndThread's interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: true
EndThread's interrupt flag: true
2
3
4
5
6
7
8
使用 Thread.interrupted()
结束线程的例子:
public class EndThread {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
while (!Thread.interrupted()) {
System.out.println(threadName + " is running");
System.out.println(threadName + "(inner)'s interrupt flag: " + isInterrupted());
}
System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("EndThread");
endThread.start();
Thread.sleep(20);
// interrupt 是中断线程,其实只是设置了线程的标识位
endThread.interrupt();
}
}
}
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
执行结果:
EndThread's interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread is running
EndThread(inner)'s interrupt flag: false
EndThread's interrupt flag: false
2
3
4
5
6
可以看出,在调用 Thread.interrupted()
后不但中断了线程,还将中断标志位改为了 false
。
还有一点需要注意的是:如果一个线程处于阻塞状态(例如调用了 sleep()
、 join()
、 wait()
方法),则在线程检查中断标志位是如果发现中断标志位为 true
,则会在这些阻塞方法调用处抛出 InterruptedException
异常,并且在抛出异常后立即将线程的中断标志位清除,即重新设置为 false
。
看下面的例子:
public class HasInterruptException {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 这里捕捉异常后,中断标识位是 false 的
System.out.println(threadName + "(in InterruptedException)'s interrupt flag: " + isInterrupted());
// 这里应当先做一些资源释放的操作,然后再调用 interrupt 方法
interrupt();
e.printStackTrace();
}
System.out.println(threadName + " is running");
}
System.out.println(threadName + "'s interrupt flag is " + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("HasInterruptException");
endThread.start();
Thread.sleep(500);
endThread.interrupt();
}
}
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
31
32
33
34
执行结果:
HasInterruptException is running
HasInterruptException is running
HasInterruptException is running
HasInterruptException is running
HasInterruptException(in InterruptedException)'s interrupt flag: false
HasInterruptException is running
HasInterruptException's interrupt flag is true
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.doomthr.ch1.base.safeend.HasInterruptException$UseThread.run(HasInterruptException.java:23)
2
3
4
5
6
7
8
9
10
原本在 Thread.sleep(100)
时被 endThread.interrupt()
中断了,在捕捉到异常后 isInterrupted()
方法被重新设置成了 false
,此时必须重新再次调用 interrupt()
来中断线程。
# 三、对 Java 线程再多一点点认识
# 1、yield() 方法
使当前线程让出CPU占有权,但让出的时间是不可设定的,也不会释放锁资源。
# 2、join() 方法
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续 行线程 B。
例如:
package com.doomthr.ch1.base;
import com.doomthr.tool.SleepTool;
/**
* Created with IntelliJ IDEA
* User: Jerry
* Date: 2019/12/30
* Time: 16:50
* Description: 演示 join 方法的使用
*/
public class UseJoin {
static class Goddess implements Runnable {
private Thread thread;
public Goddess(Thread thread) {
this.thread = thread;
}
public Goddess() {
}
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " Goddess 开始排队打饭……");
try {
if (thread != null) {
thread.join();
}
} catch (InterruptedException e) {
}
// 休眠2秒
SleepTool.second(2);
System.out.println(threadName + " Goddess 打饭完成。");
}
}
static class GoddessBoyfriend implements Runnable {
public void run() {
String threadName = Thread.currentThread().getName();
// 休眠2秒
SleepTool.second(2);
System.out.println(threadName + " GoddessBoyfriend 开始排队打饭……");
System.out.println(threadName + " GoddessBoyfriend 打饭完成。");
}
}
public static void main(String[] args) throws Exception {
Thread thread = Thread.currentThread();
GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
Thread goddessBoyfriendThread = new Thread(goddessBoyfriend);
Goddess goddess = new Goddess(goddessBoyfriendThread);
Thread goddessThread = new Thread(goddess);
goddessThread.start();
goddessBoyfriendThread.start();
String threadName = thread.getName();
System.out.println(threadName + " 开始排队打饭……");
goddessThread.join();
// 让主线程休眠2秒
SleepTool.second(2);
System.out.println(threadName + " 打饭完成。");
}
}
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
执行结果:
main 开始排队打饭……
Thread-1 Goddess 开始排队打饭……
Thread-0 GoddessBoyfriend 开始排队打饭……
Thread-0 GoddessBoyfriend 打饭完成。
Thread-1 Goddess
2
3
4
5
主线程正在打饭,结果被 Goddess
插队,紧接着又被 GoddessBoyfriend
插队,结果就是等待 GoddessBoyfriend
打饭完成后, Goddess
打饭,最后才是主线程的打饭。
# 3、线程的优先级
在Java中,通过 Thread
类的 setPriority()
方法对线程设置优先级,范围是从1~10,值越大优先级越高,所分配的时间片越多,但不能保证优先级高的线程一定优先于优先级低的线程执行,默认线程的优先级都是5。
# 4、守护线程
Daemon
(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon
线程的 时候,Java 虚拟机将会退出。可以通过调用 setDaemon(true)
将线程设置为 Daemon
线程。
Daemon
线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon
线程中的 finally
块并不一定会执行。在构建 Daemon
线程时,不能依靠 finally
块中的内容来确保执行关闭或清理资源的逻辑。
例如:
public class DaemonThread {
private static class UseThread extends Thread {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
while (!isInterrupted()) {
System.out.println(threadName + " is running");
}
System.out.println(threadName + "'s interrupt flag: " + isInterrupted());
} finally {
// 守护线程中 finally 不一定起作用
System.out.println("finally");
}
}
}
public static void main(String[] args) throws InterruptedException {
UseThread useThread = new UseThread();
// setPriority 是设置线程的优先级,范围是1到10,值越大优先级越高,默认是5,不一定起作用
// useThread.setPriority(1);
// 设置守护线程为 true 时,当主线程结束时,守护线程也结束
useThread.setDaemon(true);
useThread.start();
Thread.sleep(5);
// useThread.interrupt();
}
}
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 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
Thread-0 is running
2
3
4
5
6
7
8
可以看出 finally
中的代码并没有得到执行,以为主线程执行完毕就结束了,守护线程也跟着结束了。要想执行守护线程 finally
中的代码,需要手动的调用守护线程的 interrupt()
方法。