目录
[Thread 类其他的属性和方法~~](#Thread 类其他的属性和方法~~)
[Thread 构造方法概览:](#Thread 构造方法概览:)
[name 属性说明:](#name 属性说明:)
[Thread 的几个常见属性](#Thread 的几个常见属性)
[前台线程 vs 后台线程](#前台线程 vs 后台线程)
[启动一个线程 - start()](#启动一个线程 - start())
[启动一个线程 - start()](#启动一个线程 - start())
[变量捕获与 lambda / 匿名内部类](#变量捕获与 lambda / 匿名内部类)
[Thread 提供更靠谱的方案实现上述的效果](#Thread 提供更靠谱的方案实现上述的效果)
[日常开发中 catch 中的逻辑一般不会这么写~~](#日常开发中 catch 中的逻辑一般不会这么写~~)
Thread 类其他的属性和方法~~
Thread 构造方法概览:
-
Thread()创建线程对象。
-
Thread(Runnable target)使用 Runnable 对象创建线程对象。
-
Thread(String name)创建线程对象,并命名。
-
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名。
-
【了解】
Thread(ThreadGroup group, Runnable target)使用 Runnable 对象创建线程对象,并分组。
说明:线程组可以方便批量管理,分组的组别为线程组。多用在底层代码开发中。
批注 :
name给线程起名字------不影响线程的执行,方便调试~~
name 属性说明:
默认线程名字为 Thread-数字(如 Thread-0, Thread-1等)。
代码示例:
java
Thread t3 = new Thread(() -> {
while (true) {
System.out.println("t3");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t3");
Thread 的几个常见属性
| 属性 | 获取方法 | 说明 |
|---|---|---|
| ID | getId() |
ID 是线程的唯一标识,不同线程不会重复 |
| 名称 | getName() |
全是给程序员调试工具用 |
| 状态 | getState() |
状态表示线程所处的某个情况,下面我们会进一步说明 |
| 优先级 | getPriority() |
优先级高的线程理论上更容易被调度到 |
| 是否后台线程 | isDaemon() |
关于后台线程,再说后一点。JVM 会在所有程序的所有后台线程结束后,才会结束运行。是否后台,但简单的理解,为 true 方法是否运行起来 |
| 是否存活 | isAlive() |
线程的中断问题,下面我们进一步说明 |
补充说明:
线程组(ThreadGroup)现在很少涉及到,后续介绍"线程池"生态位上替代了线程组~~
main线程包括代码中手动创建的线程,默认都是前台线程~~返回 0 表示进程执行成功,非 0 值表示失败,使用不同的值表示不同的错误原因~~
学习 C 语言的时候,
hello world中的int main() { return 0; },进程结束返回码~~
前台线程 vs 后台线程
-
前台线程:会阻止整个进程结束。
举例:李局长要是撤了,宴席就会结束了(主角退场)。
-
后台线程:不会阻止整个进程结束。
举例:如果我吃饱了,我说我要撤了,是否会使宴席结束呢?------后台线程~~
一个进程中的前台线程,不止有一个~~
得是所有的前台线程都结束了,进程才结束~~
后台线程:也叫做"守护线程""默默"背后默默的守护~~
"等你下课" 🎵
启动一个线程 - start()
start操作本质上会调用操作系统提供的 API,在操作系统的内部(内核)创建出一个线程出来了。
java
private native void start0();
-
Java 的代码 =>
.class,再经过 JVM 解释执行的~~ -
带有
native字样的方法,就是在 JVM 内部通过 C++ 实现的方法。 -
调用操作系统的原生 API 创建线程,根据当前的操作系统做区分~~
-
内部是一系列的
#if、#ifdef、#else if、#else(C/C++来说) -
操作系统内部,通过 PCB 来描述,通过链表组织~~
-
对于 Linux 来说(Linux 开源,Windows 闭源)
"被调度起来后执行的逻辑,就是 run 里面设定的刚刚逻辑~~"
图示说明:
操作系统拿着 PCB 结构体来进行调度执行~~
pid:每个 PCB 对应一个线程,在 PCB 上有一个特质的id属性,这个id是相同同的,也就是一个进程~~
start 方法本身,执行速度非常快~~
start针对一个 thread 对象,只能调用一次~~Java 设定了,要让一个 thread 对象和一个操作系统的线程一一对应~~
中断一个线程
英文术语:
Interrupt------ 我个人更喜欢称为"终止"
对于 Java 来说,一个线程终止,就是这个线程的入口方法执行完毕~~
Java 并不提供"强制终止"(所有的让线程终止的做法,都需要等待着"让入口方法结束")
有些危险:
A 线程调用方法,终止 B 线程。
调用 A 的终止方法时,无法确定 B 线程当前执行到哪个环节了~~
做某个事情,做了一半,就被强制终止了~~
启动一个线程 - start()
之前我们已经看到了如何通过重写 run方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。
-
重写
run方法是提供给线程要做的事情的命令清单。 -
线程对象可以认为是把 李四、王五叫过来了。
-
而调用
start()方法,就是喊一声:"劳动啦!",线程才真正独立去执行了。
流程图说明:
学员 A(新线程):执行逻辑、响应主线程的通知、A 线程结束。
老师(主线程):开始新线程、去查看任务结果、等待 A 线程结束、汇总到评分系统。
调用 start 方法,才真的在操作系统的层面创建出一个线程。
变量捕获与 lambda / 匿名内部类
**写作局部变量,此时无法编译通过~~**
变量捕获:lambda / 匿名内部类
捕获的变量,必须是
final/ 事实 final
错误示例:
java
boolean running = true;
Thread t = new Thread(() -> {
while (running) { // 编译错误:Variable used in lambda expression should be final or effectively final
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程退出");
});
t.start();
**写作成员变量,情况就不一样~~**
触发的做法不再是"变量捕获",而是"内部变量向外部变成了类"。
正确示例(使用成员变量):
java
private static boolean running = true;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (running) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程退出");
});
t.start();
// 主线程中,让用户选择是否让 t 线程终止
Scanner sc = new Scanner(System.in);
System.out.println("请输入整数,0 表示不让 t 线程终止:");
int n = sc.nextInt();
if (n == 0) {
running = false;
}
}
说明:
- lambda 本质上是基于函数式接口的匿名内部类。
C++ 的考虑:程序自行负责保证变量的生命周期匹配~~
Java 的考虑:变量的生命周期由 JVM 托管,更加安全~~
一旦修改了局部变量的生命周期可能出现不一致,代码更乱。
为了避免混乱,Java 直接禁止修改~~
Thread 提供更靠谱的方案实现上述的效果
获取当前线程的引用:
这个 lambda 在哪个线程中执行的,得到的引用就是哪个线程
Thread引用~~
java
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程退出");
示例完整代码:
java
package thread;
import java.util.Scanner;
public class Demo9 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
Thread cur = Thread.currentThread();
while (!cur.isInterrupted()) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入整数表示让线程 t 结束:");
int n = scanner.nextInt();
if (n == 0) {
// 这个方法不光可以设置标志位,还能唤醒 sleep 等导致线程阻塞的方法
// 会使 sleep 漏出异常,InterruptedException
t.interrupt();
}
}
}
执行逻辑说明:
执行这个代码的时候,一定是先针对
Thread t中的参数,先进行求值。在执行
Thread的构造方法。在把
Thread构造方法的结果,赋值给t(定义 + 符合)
图示说明:
Thread cur = Thread.currentThread();
this表示当前对象
currentThread表示当前线程
Thread 对象内部,封装了一个 boolean 变量
效果:这个和手动修改标志位类似。
还额外做了其他操作,把
sleep这样的方法唤醒。
异常信息(将来常用):
java
Exception in thread "t 线程" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at thread.Demo9.lambda$main$0(Demo9.java:14)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at thread.Demo9.lambda$main$0(Demo9.java:11)
... 1 more
Process finished with exit code 0
日常开发中 catch 中的逻辑一般不会这么写~~
IDEA 生成的爆栈代码~~
java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
实现开发中 catch 根据阻塞的不同,采取不同的动作:
-
尝试重试~~
-
记录错误 / 日志~~(记到日志文件,写入到日志服务器中)
-
触发报警 / 短信~~(钉钉群、企业微信、邮件...)