1.什么是线程?
线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
- 进程就是一个独立程序,像一个公司,拥有独立内存,不能互相共享资源。
- 线程就是进程里的执行单元,像公司里的员工。
- 同一进程内的线程:
- 堆内存、方法区共享(可以共享数据)
- 栈内存独立(每个线程有自己的栈)
- 启动多个线程 = 开多个栈 = 同时执行 = 多线程并发。
- 多线程的目的就是提高程序效率
1、你敲 java HelloWorld 回车后发生了什么?
java HelloWorld
这个命令一执行:操作系统先启动 JVM,JVM 本身就是电脑里的一个独立进程。进程:可以简单理解成正在运行的程序,电脑打开的软件都是进程。
2、JVM 启动后,立刻开 2 个线程
进程里面可以包含多个线程,线程就是进程里干活的执行单元。你写的 Java 程序,只要运行,天生最少自带 2 个线程:
线程 1:主线程(main 线程)
-
作用:专门运行你代码里的
main方法 -
你所有写的业务代码、顺序执行的代码,全部在主线程里跑
-
程序入口
public static void main就是主线程的入口方法
线程 2:垃圾回收线程(GC 线程)
-
Java 独有的后台守护线程
-
作用:全程在后台默默运行,自动回收没用的对象内存
-
不用你手动管内存释放,它一直盯着,垃圾自动清掉
3、重点:什么叫「并发」?
这两个线程同时在运行,互不耽误:
-
主线程在认认真真执行你的代码
-
GC 垃圾回收线程在后台偷偷清理内存它们交替抢占 CPU,看起来就是同时工作,这就叫并发。
2.什么是多线程?
多线程是java中的一种并发编程技术,允许程序同时执行多个任务。每个线程代表一个独立的执行路径,共享进程的资源(如内存),但拥有独立的栈空间。多线程可以提高程序效率,尤其在处理I/O密集型或CPU密集型任务时。
多线程的实现方式
继承Thread类
通过继承Thread类并重写run()方法实现线程逻辑。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 启动线程
MyThread thread = new MyThread();
thread.start();
实现Runnable接口
更推荐的方式,避免单继承限制,适合资源共享。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 启动线程
Thread thread = new Thread(new MyRunnable());
thread.start();
单继承限制:
Java 类只能单继承,继承Thread后就无法继承其他类;实现Runnable是接口实现,不占用继承名额,类还可以继承别的父类,灵活度更高。
线程创建和业务代码分开,Thread只负责开启线程,Runnable只写业务,代码耦合更低。
实现Callable接口
支持返回结果和抛出异常,通常与FutureTask配合使用。
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
// 使用FutureTask获取结果
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println(task.get()); // 输出结果
3.线程的生命周期
-
新建(New):线程对象创建但未启动。
-
就绪(Runnable):调用
start()后,线程等待CPU调度。 -
运行(Running):线程获得CPU资源执行
run()方法。 -
阻塞(Blocked):线程因等待锁、I/O等操作暂停执行。
-
终止(Terminated):
run()方法执行完毕或异常终止。
逐个详细讲解(每个状态 + 含义 + 什么时候处于这状态)
1. NEW 新建状态
new Thread()
-
代码里刚
new出线程对象,还没调用 start () -
只是在堆里创建了对象,线程根本没启动,没有栈内存
-
操作系统还不认识这个线程
2. RUNNABLE 就绪 + 运行状态
调用 thread.start() 之后进入此状态。Java 把就绪、运行合并成这一个状态。
-
就绪:线程已经准备好,抢到 CPU 时间片就能跑
-
运行:正在 CPU 上执行
run()方法里的代码
压栈、弹栈 全部发生在这个状态!run 方法调用 → 压栈;run 内方法执行完弹栈。
3. BLOCKED 阻塞状态(锁阻塞)
-
线程想要获取 synchronized 锁,但是锁被别的线程抢走了
-
只能在门外等着,不能占用 CPU
-
等别的线程释放锁,它抢到锁,就回到 RUNNABLE
4. WAITING 无限等待(无时间等待)
调用这些方法进入:Object.wait() (无参)
-
线程主动停下,无限等待
-
不占用 CPU,不干活
-
必须别人唤醒:
notify()/notifyAll()才能回到 RUNNABLE
5. TIMED_WAITING 限时等待(有时间等待)
调用这些方法进入:Thread.sleep(毫秒)、Object.wait(时间)
-
线程等待指定时间
-
时间一到,自动醒来,回到 RUNNABLE
-
日常用的最多的就是
sleep()
6. TERMINATED 终止(死亡)状态
线程彻底结束,生命周期走完进入条件:
-
run()方法全部执行完毕正常结束 -
线程被异常终止一旦死亡,不能再次 start () 重启,线程一次性的。
new Thread()只是在堆里创建了线程对象,线程还没活,没有栈,属于NEW新建状态。调用
t.start()之后JVM 底层做 3 件事:① 给该线程开辟独立的栈内存② 线程状态变为RUNNABLE(就绪)③ CPU 调度到它时,自动调用run()方法重点:
start()本身不直接运行代码,它只是激活线程!真正执行业务代码的是run()。
需要注意点:
使用了多线程机制之后,main方法结束,可能程序不会结束
深度原理分析
main方法 = 主线程程序启动时 JVM 创建的主线程,入口就是main方法。main方法代码执行完、return 结束 → 主线程死亡、主栈清空。但是!JVM 什么时候关闭?JVM 退出的条件:
其他自定义线程,每个都有自己独立的栈,主线程栈空了、结束了,别的线程的栈依然在正常压栈、弹栈、运行代码,互不影响所有的非守护线程全部死亡,JVM 才会结束只要还有任意一个普通线程在运行,哪怕主线程没了,整个程序依然活着。
3、通俗比喻(结合你公司员工比喻)
JVM = 公司
主线程 = 老板
其他子线程 = 员工
main方法结束= 老板下班走了但是公司里的员工还在干活、上班 → 公司不会关门只有所有员工全部下班,公司才会关闭(JVM 退出)。代码示例:
public class Test { public static void main(String[] args) { // 这是主线程运行的代码 // 开启一个新的子线程(分支线程) new Thread(() -> { // 这里的代码 属于 子线程 while(true){ // 死循环:永远一直跑,停不下来 } }).start(); // 启动子线程 // 下面这句还是主线程的代码 System.out.println("主线程结束了"); // main方法执行完毕 → 主线程死亡 } }2、这段代码运行时发生了什么?
① 程序启动 → JVM 创建 主线程
运行
main方法,主线程开始跑。② 主线程执行到
new Thread(...).start()JVM 又创建了一个 子线程,并且让它开始运行。
③ 两个线程同时、独立、互不干扰运行
主线程:飞快执行
println,然后main结束 → 主线程死亡子线程:进入
while(true)死循环 → 永远在运行④ 最终结果
控制台会打印:
主线程结束了但是!程序不会停止!因为子线程还在死循环跑着。
4.成员方法
| 方法名称 | 说明 |
| String getName() | 返回此线程的名称 |
|
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
| static Thread currentThread() | 获取当前线程的对象 |
| static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
| setPriority(int newPriority) | 设置线程的优先级 |
| final int getPriority() | 获取线程的优先级 |
| final void setDaemon(boolean on) |
设置为守护线程 |
没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始)
1. sleep () 和 wait () 的区别
表格
特性
Thread.sleep()
Object.wait()
所属类
Thread 静态方法
Object 成员方法
锁资源
不释放锁
释放锁
使用场景
单纯的延时、暂停
线程间通信(等待 / 通知)
唤醒方式
时间到自动唤醒
必须用 notify ()/notifyAll () 唤醒
线程状态
TIMED_WAITING
WAITING/TIMED_WAITING
2. 中断休眠
其他线程可以调用
线程对象.interrupt()中断正在休眠的线程,会立即抛出 InterruptedException,结束休眠。
2.优先级方法的调用
Java 线程优先级用 1~10 的整数表示,Thread 类自带三个常量:
Thread.MIN_PRIORITY= 1 最低优先级Thread.NORM_PRIORITY= 5 默认优先级(所有线程默认都是 5)Thread.MAX_PRIORITY= 10 最高优先级
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("低优先级线程");
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("高优先级线程");
}
});
// 设置优先级
t1.setPriority(1); // 最低
t2.setPriority(10); // 最高
t1.start();
t2.start();
}
}
-
优先级不能控制执行顺序优先级高 = 抢到 CPU 概率大,优先级低 = 抢到 CPU 概率小绝不是:高优先级线程执行完,低优先级才执行。
-
主线程默认优先级也是 5
-
子线程继承父线程优先级主线程创建的子线程,默认优先级和主线程一样(5)。
-
优先级设置必须在 start () 之前线程启动之后再
setPriority()基本无效
5.Thread.yield () 方法详解
1. 官方定义
yield():礼让线程
Thread.yield();
作用:当前线程主动让出 CPU 时间片,从运行态回到就绪态,重新参与 CPU 抢占。
简单人话:
我现在不抢 CPU 了,先歇一下,大家重新排队抢 CPU,我也在队伍里,下一轮我依然有可能再次抢到 CPU。
2. 核心特点
-
不会释放锁!!!最重要这是 90% 人搞错的点:
yield()只是让出 CPU- 持有锁的线程调用 yield,锁依然握在手里不释放
- 其他线程依然进不来同步代码块
-
线程状态变化运行态 (Running) → 就绪态 (Runnable)不是阻塞态!不是休眠!
-
只是建议调度器切换线程,JVM 不一定听从yield 只是给操作系统线程调度器一个建议:我空闲了,可以调度别人了。调度器完全可以忽略,马上又把 CPU 分给这个线程。
代码解释:
public class YieldTest {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
Thread.yield(); // 礼让
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}, "B").start();
}
}
现象:线程 A 每打印一次就礼让,B 会穿插执行,但顺序不稳定,A 礼让后依旧可能立刻抢到 CPU。
反例对比:
wait():释放 CPU + 释放锁,进入等待队列sleep():释放 CPU 时间片+ 不释放锁yield():让出 CPU + 不释放锁
6、synchronized 关键字的作用
synchronized 是 Java 中的关键字,用于实现线程同步,确保多线程环境下对共享资源的访问安全。它可以修饰方法或代码块,防止多个线程同时访问同一资源,从而避免数据不一致或竞态条件。
synchronized 的用法
修饰实例方法
当 synchronized 修饰实例方法时,锁的是当前实例对象(this)。同一时间只有一个线程能访问该对象的 synchronized 方法,其他线程会被阻塞。
public synchronized void method() {
// 同步代码
}
修饰静态方法
当 synchronized 修饰静态方法时,锁的是当前类的 Class 对象。同一时间只有一个线程能访问该类的静态同步方法。
public static synchronized void staticMethod() {
// 同步代码
}
修饰代码块
synchronized 还可以修饰代码块,并指定锁对象。锁对象可以是任意对象实例或类的 Class 对象。
public void blockMethod() {
synchronized (this) { // 锁当前实例
// 同步代码
}
synchronized (ClassName.class) { // 锁类的 Class 对象
// 同步代码
}
}
synchronized 的原理
synchronized 的实现基于 JVM 的监视器锁(Monitor)。每个对象都有一个关联的 Monitor,线程进入 synchronized 代码块时会尝试获取 Monitor 的所有权,退出时释放所有权。
- 进入同步块:线程通过
monitorenter指令获取锁。 - 退出同步块:线程通过
monitorexit指令释放锁。
synchronized 的特性
- 可重入性:同一线程可以多次获取同一把锁,避免死锁。
- 不可中断性:一旦线程获取锁,其他线程必须等待,直到锁被释放。
- 公平性:默认是非公平锁,但可以通过
ReentrantLock实现公平锁。
示例代码(修饰代码块中的自定义锁对象方法)
public class LockAndCpuTest {
// 唯一锁对象,所有线程抢这一把锁
private static final Object lock = new Object();
public static void main(String[] args) {
// 线程A
new Thread(() -> {
while (true) {
synchronized (lock) {
System.out.println("【线程A】抢到锁,正在执行");
try {
Thread.sleep(1000); // 占用1秒,方便看现象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【线程A】释放锁了");
}
}, "A").start();
// 线程B
new Thread(() -> {
while (true) {
synchronized (lock) {
System.out.println("【线程B】抢到锁,正在执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【线程B】释放锁了");
}
}, "B").start();
}
}
运行现象
输出永远只会这样交替:
【线程A】抢到锁,正在执行
【线程A】释放锁了
【线程B】抢到锁,正在执行
【线程B】释放锁了
完整底层流程拆解
- 一开始 A、B 都在就绪队列,一起抢 CPU操作系统调度,随机先把 CPU 分给 A
- A 拿到 CPU,开始跑代码
- A 走到
synchronized(lock),尝试获取锁锁空闲 → A 拿到锁,执行内部代码 - A 睡 1 秒,全程持有锁
- 此时 线程 B 也抢到 CPU 了!!B 拿到 CPU 之后,走到
synchronized(lock)发现锁被 A 拿着 → 抢锁失败→ 立刻放弃 CPU,进入锁阻塞队列,不再运行 - A 睡醒,退出同步代码块 → 释放锁
- 锁空闲后,阻塞队列里的 B 被唤醒,回到就绪态
- 下次 CPU 调度到 B,B 就能拿到锁执行
结合 yield () 的改造示例
public class YieldNotReleaseLock {
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
System.out.println("线程A拿到锁");
Thread.yield(); // 礼让,让出CPU
System.out.println("线程A再次抢到CPU,锁还在手里!");
}
}, "A").start();
new Thread(() -> {
System.out.println("线程B尝试抢CPU");
synchronized (lock) {
System.out.println("线程B拿到锁");
}
}, "B").start();
}
}
yield 代码运行结果:
线程A拿到锁
线程B尝试抢CPU
// 此处A调用yield,让出CPU
// 但!锁没放!B就算抢到CPU,也进不来锁!
线程A再次抢到CPU,锁还在手里!
线程A释放锁
线程B拿到锁
锁之间的安全问题
简单说:用了多把锁,结果本该互斥的代码没互斥,导致多线程同时修改共享数据,数据错乱、线程不安全。
两大核心安全问题
问题 1:锁对象不一致 → 锁失效(最常见)
现象
多个线程用不同的锁,导致同步代码不互斥,同时执行,数据乱掉。
举例:共享变量 count,要保证自增安全
Object lock1 = new Object();
Object lock2 = new Object();
// 线程A
new Thread(()->{
synchronized(lock1){
count++;
}
}).start();
// 线程B
new Thread(()->{
synchronized(lock2){
count++;
}
}).start();
安全问题分析
-
lock1、lock2 是两把独立锁
-
线程 A 拿锁 1,线程 B 拿锁 2
-
互不阻塞、同时运行
-
同时修改
count→ 线程不安全,数据错误
结论
只要锁对象不一样,synchronized 形同虚设!共享资源必须共用同一把锁才安全。
问题 2:锁粒度混乱(this 锁、class 锁混用)
对应三种 synchronized:
-
实例方法锁:锁
this -
静态方法锁:锁
类.class -
代码块自定义锁
安全问题:
this 锁 和 class 锁 是两把完全不同的锁!互不干涉!
// 实例方法锁:锁 this
public synchronized void add(){}
// 静态方法锁:锁 Test.class
public static synchronized void sub(){}
- 线程调用
add() - 线程同时调用
sub()两个方法会同时执行!互不阻塞
结论:
- 实例锁(this)只管住实例同步方法
- 静态锁(class)只管住静态同步方法
- 两者之间毫无互斥关系,同时执行照样不安全
开发中正确的安全锁规范
针对同一个共享资源(比如计数器、库存、用户数据):
- 所有操作它的代码,必须共用同一把锁
- 锁对象要写成:
private static final Object lock = new Object();static:所有对象共用这一把锁final:锁对象引用不变,不会换锁
- 绝对不要:
- 不要用
new Object()临时当锁 - 不要每个线程新建锁
- 不要混用 this 锁、class 锁
- 不要随便用字符串常量当锁
- 不要用
7、线程的join方法
join () 是什么?
Thread.join()线程插队、等待线程执行完毕
直白人话:
主线程 / 当前线程 停下来,等待调用 join () 的这个线程跑完、结束死亡之后,自己再继续往下执行。
代码示例:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程:" + i);
try { Thread.sleep(500); }
catch (Exception e) {}
}
});
t.start();
// 主线程调用 t.join()
t.join();
// 下面这句,**必须等子线程全部跑完才会打印**
System.out.println("子线程结束,主线程继续执行");
}
}
运行结果:
永远顺序:子线程 1、2、3、4、5 全部打完→ 才打印主线程最后一句话
核心原理:
-
谁调用 join,就等谁死 :
A线程里写B.join()→ A 暂停,等待 B 线程全部执行完毕、死亡,A 才恢复运行 -
底层本质:wait ()
join()源码内部就是调用了Object.wait()所以:join () 会释放锁!!!
再补一个带锁的例子,验证 join 释放锁
public class JoinReleaseLock {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("t1拿到锁");
try { Thread.sleep(1000); } catch (Exception e) {}
System.out.println("t1执行完毕");
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2拿到锁");
}
});
t1.start();
t1.join(); // 主线程等t1,并且**释放锁**
t2.start();
}
现象:t1 跑完 → 锁释放 → t2 顺利拿到锁
8、Object类的wait()、notify()、notifyAll()方法
方法详细说明
1. wait()
-
让当前线程进入等待队列
-
自动释放持有的锁(最关键)
-
线程状态:
RUNNABLE → WAITING -
必须被
notify()/notifyAll()唤醒
重载:
-
wait():无限等待,必须被唤醒 -
wait(long millis):超时自动醒
2. notify()
-
随机唤醒一个在该对象上等待的线程
-
不立即释放锁!要等同步代码执行完才释放
-
唤醒后线程进入就绪态,重新抢锁
3. notifyAll()
-
唤醒所有在该对象上等待的线程
-
所有线程一起抢锁
-
实际开发推荐用 notifyAll (),更安全,避免死等
必须遵守的 3 条铁律
必须在
synchronized同步代码 / 方法里调用 , 不在锁里调用 → 直接抛IllegalMonitorStateException必须是 锁对象 调用例如:
synchronized(lock)→ 必须用lock.wait()、lock.notify()wait () 会释放锁,notify () 不会立刻释放锁
代码示例:
public class WaitTest {
private static final Object lock = new Object();
public static void main(String[] args) {
// 线程A:等待
new Thread(() -> {
synchronized (lock) {
System.out.println("A拿到锁");
try {
lock.wait(); // 等待,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A被唤醒,继续执行");
}
}, "A").start();
// 线程B:唤醒
new Thread(() -> {
try { Thread.sleep(1000); } catch (Exception e) {}
synchronized (lock) {
System.out.println("B拿到锁");
lock.notify(); // 唤醒等待的线程A
System.out.println("B唤醒了A");
}
}, "B").start();
}
}
执行流程
- A 抢到 CPU,拿到锁
- A 调用
wait()→ 释放锁 + 让出 CPU - B 抢到 CPU,顺利拿到锁
- B 调用
notify()唤醒 A - B 执行完同步代码块,释放锁
- A 被唤醒,回到就绪态,重新抢 CPU、抢锁,继续执行
四大线程方法对比
| 方法 | 所属类 | 是否让出 CPU | 是否释放锁 | 线程状态 | 唤醒条件 |
|---|---|---|---|---|---|
sleep() | Thread | 让出 | 不释放 | 阻塞 | 时间到 |
yield() | Thread | 让出 | 不释放 | 就绪 | 重新抢 CPU |
join() | Thread | 让出 | 释放锁 | 等待阻塞 | 目标线程死亡 |
wait() | Object | 让出 | 释放锁 | 等待阻塞 | notify/notifyAll |
9、Java 线程安全类 & 非线程安全类
1. 非线程安全类(速度快,无锁)
代表:ArrayList、HashMap、StringBuilder
-
内部没有任何锁
-
多线程同时改,数据错乱、丢数据、炸异常
-
执行效率极高
2. 线程安全类(慢,加锁保护)
代表:Vector、Hashtable、StringBuffer
-
内部自带 synchronized 锁
-
多线程同时操作不会乱
-
同一时间只有一个线程能改,性能差
10、什么是死锁
直白定义
多个线程,互相持有对方需要的锁,并且都不肯释放自己手里的锁,无限等待,程序卡死不动。
死锁必须同时满足 4 个必要条件(面试必背)
这四个缺一不可,只要破坏任意一个,死锁就不会发生。
互斥条件一把锁同一时间只能被一个线程持有,别人抢不到。(synchronized 天生满足)
请求保持条件线程已经拿着一把锁了,不释放手里的锁,又去申请别人的锁。
不可剥夺条件锁只能持有者主动释放,别人不能强行抢走锁。(synchronized 天生满足)
循环等待条件线程 A 等线程 B 的锁,线程 B 等线程 A 的锁,形成环形等待。
死锁经典代码示例(标准面试代码)
两把锁,两个线程,完美死锁
public class DeadLockTest {
// 锁1、锁2 两个独立对象锁
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程A:先拿lock1,再要lock2
new Thread(() -> {
synchronized (lock1) {
System.out.println("线程A拿到锁1");
try { Thread.sleep(1000); } catch (Exception e) {}
// 不释放lock1,继续请求lock2
synchronized (lock2) {
System.out.println("线程A拿到锁2");
}
}
}, "A").start();
// 线程B:先拿lock2,再要lock1
new Thread(() -> {
synchronized (lock2) {
System.out.println("线程B拿到锁2");
try { Thread.sleep(1000); } catch (Exception e) {}
// 不释放lock2,继续请求lock1
synchronized (lock1) {
System.out.println("线程B拿到锁1");
}
}
}, "B").start();
}
}
运行结果(卡死)
线程A拿到锁1
线程B拿到锁2
然后程序永远卡住,不再输出任何内容
卡死原因拆解
-
A 持有
lock1,等待lock2 -
B 持有
lock2,等待lock1 -
互相等待,都不释放自己手里的锁
-
满足全部 4 个死锁条件 → 死锁
11、守护线程
1. 线程分类对比
-
用户线程(默认):main主线程、业务线程,JVM需等待所有用户线程全部结束后才会退出
-
守护线程(后台线程):后台辅助线程,需手动设置,依附于用户线程生存,不影响JVM退出
2. 核心特性(灵魂要点)
所有用户线程全部结束 → JVM立即退出 → 守护线程被强制终止(无论任务是否执行完毕)
代码示例:
public class DaemonTest {
public static void main(String[] args) {
// 用户线程(默认)
Thread userThread = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("用户线程:" + i);
try { Thread.sleep(1000); } catch (Exception e) {}
}
});
// 守护线程
Thread daemonThread = new Thread(() -> {
while (true) { // 无限循环
System.out.println("守护线程后台运行中...");
try { Thread.sleep(1000); } catch (Exception e) {}
}
});
daemonThread.setDaemon(true); // 设置守护线程
userThread.start();
daemonThread.start();
}
}
运行现象
-
守护线程一直在无限循环打印
-
用户线程跑完 5 次结束
-
瞬间!JVM 退出,守护线程直接被杀死,循环强行终止程序直接结束,不会等守护线程跑完。
守护线程高频注意事项(面试重点)
-
设置时机:setDaemon(true) 必须在start()之前,线程启动后无法修改守护状态
-
finally执行:守护线程被强制终止时,finally代码块不一定执行(可能来不及运行)
-
CPU与锁:守护线程仍会参与CPU抢占、锁竞争,也可能发生死锁(与普通线程规则一致)
-
状态继承:父线程是守护线程,其创建的子线程默认也是守护线程
-
禁用场景:禁止用于执行文件写入、数据库操作、资源关闭等任务(易导致数据损坏、资源泄露)
-
常用场景:日志收集、心跳检测、缓存清理、JVM自带的GC(垃圾回收)线程
当守护线程正在执行任务时,若所有用户线程全部结束,JVM 会直接强制终止守护线程,此时守护线程中 finally 代码块可能来不及执行,并非一定能执行完成。
关键补充
-
正常用户线程:执行到 finally 代码块时,会确保执行完毕(除非程序异常终止);
-
守护线程:因 JVM 退出是 “强制终止”,不会等待守护线程的 finally 代码执行,可能导致 finally 中用于关闭资源、释放锁的代码未运行,进而引发资源泄露。
欢迎大家评论交流,一起加油!!!

1581

被折叠的 条评论
为什么被折叠?



