java-线程和进程

1.什么是线程?

线程:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

  • 进程就是一个独立程序,像一个公司,拥有独立内存,不能互相共享资源。
  • 线程就是进程里的执行单元,像公司里的员工。
  • 同一进程内的线程:
    • 堆内存、方法区共享(可以共享数据)
    • 栈内存独立(每个线程有自己的栈)
  • 启动多个线程 = 开多个栈 = 同时执行 = 多线程并发。
  • 多线程的目的就是提高程序效率

1、你敲 java HelloWorld 回车后发生了什么?

java HelloWorld

这个命令一执行:操作系统先启动 JVM,JVM 本身就是电脑里的一个独立进程。进程:可以简单理解成正在运行的程序,电脑打开的软件都是进程。

2、JVM 启动后,立刻开 2 个线程

进程里面可以包含多个线程,线程就是进程里干活的执行单元。你写的 Java 程序,只要运行,天生最少自带 2 个线程

线程 1:主线程(main 线程)

  • 作用:专门运行你代码里的 main 方法

  • 你所有写的业务代码、顺序执行的代码,全部在主线程里跑

  • 程序入口 public static void main 就是主线程的入口方法

线程 2:垃圾回收线程(GC 线程)

  • Java 独有的后台守护线程

  • 作用:全程在后台默默运行,自动回收没用的对象内存

  • 不用你手动管内存释放,它一直盯着,垃圾自动清掉

3、重点:什么叫「并发」?

这两个线程同时在运行,互不耽误:

  1. 主线程在认认真真执行你的代码

  2. 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.线程的生命周期

  1. 新建(New):线程对象创建但未启动。

  2. 就绪(Runnable):调用start()后,线程等待CPU调度。

  3. 运行(Running):线程获得CPU资源执行run()方法。

  4. 阻塞(Blocked):线程因等待锁、I/O等操作暂停执行。

  5. 终止(Terminated)run()方法执行完毕或异常终止。

逐个详细讲解(每个状态 + 含义 + 什么时候处于这状态)

1. NEW 新建状态

new Thread()
  • 代码里刚 new 出线程对象,还没调用 start ()

  • 只是在堆里创建了对象,线程根本没启动,没有栈内存

  • 操作系统还不认识这个线程

2. RUNNABLE 就绪 + 运行状态

调用 thread.start() 之后进入此状态。Java 把就绪、运行合并成这一个状态。

  1. 就绪:线程已经准备好,抢到 CPU 时间片就能跑

  2. 运行:正在 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 终止(死亡)状态

线程彻底结束,生命周期走完进入条件:

  1. run() 方法全部执行完毕正常结束

  2. 线程被异常终止一旦死亡,不能再次 start () 重启,线程一次性的。

  1. new Thread()只是在堆里创建了线程对象,线程还没活,没有栈,属于 NEW 新建状态。

  2. 调用 t.start() 之后JVM 底层做 3 件事:① 给该线程开辟独立的栈内存② 线程状态变为 RUNNABLE(就绪)③ CPU 调度到它时,自动调用 run() 方法

重点:start() 本身不直接运行代码,它只是激活线程!真正执行业务代码的是 run()

需要注意点:

使用了多线程机制之后,main方法结束,可能程序不会结束

深度原理分析

  1. main 方法 = 主线程程序启动时 JVM 创建的主线程,入口就是main方法。main方法代码执行完、return 结束 → 主线程死亡、主栈清空

  2. 但是!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 类自带三个常量:

  1. Thread.MIN_PRIORITY = 1 最低优先级
  2. Thread.NORM_PRIORITY = 5 默认优先级(所有线程默认都是 5)
  3. 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. 核心特点

  1. 不会释放锁!!!最重要这是 90% 人搞错的点:

    • yield() 只是让出 CPU
    • 持有锁的线程调用 yield,锁依然握在手里不释放
    • 其他线程依然进不来同步代码块
  2. 线程状态变化运行态 (Running) → 就绪态 (Runnable)不是阻塞态!不是休眠!

  3. 只是建议调度器切换线程,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 的特性

  1. 可重入性:同一线程可以多次获取同一把锁,避免死锁。
  2. 不可中断性:一旦线程获取锁,其他线程必须等待,直到锁被释放。
  3. 公平性:默认是非公平锁,但可以通过 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】释放锁了

完整底层流程拆解

  1. 一开始 A、B 都在就绪队列,一起抢 CPU操作系统调度,随机先把 CPU 分给 A
  2. A 拿到 CPU,开始跑代码
  3. A 走到 synchronized(lock)尝试获取锁锁空闲 → A 拿到锁,执行内部代码
  4. A 睡 1 秒,全程持有锁
  5. 此时 线程 B 也抢到 CPU 了!!B 拿到 CPU 之后,走到 synchronized(lock)发现锁被 A 拿着 → 抢锁失败立刻放弃 CPU,进入锁阻塞队列,不再运行
  6. A 睡醒,退出同步代码块 → 释放锁
  7. 锁空闲后,阻塞队列里的 B 被唤醒,回到就绪态
  8. 下次 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();

安全问题分析

  1. lock1、lock2 是两把独立锁

  2. 线程 A 拿锁 1,线程 B 拿锁 2

  3. 互不阻塞、同时运行

  4. 同时修改 count线程不安全,数据错误

结论

只要锁对象不一样,synchronized 形同虚设!共享资源必须共用同一把锁才安全。

问题 2:锁粒度混乱(this 锁、class 锁混用)

对应三种 synchronized:

  1. 实例方法锁:锁 this

  2. 静态方法锁:锁 类.class

  3. 代码块自定义锁

安全问题:

this 锁 和 class 锁 是两把完全不同的锁!互不干涉!

// 实例方法锁:锁 this
public synchronized void add(){}

// 静态方法锁:锁 Test.class
public static synchronized void sub(){}
  • 线程调用 add()
  • 线程同时调用 sub()两个方法会同时执行!互不阻塞

结论:

  • 实例锁(this)只管住实例同步方法
  • 静态锁(class)只管住静态同步方法
  • 两者之间毫无互斥关系,同时执行照样不安全

开发中正确的安全锁规范

针对同一个共享资源(比如计数器、库存、用户数据):

  1. 所有操作它的代码,必须共用同一把锁
  2. 锁对象要写成:
    private static final Object lock = new Object();
    
    • static:所有对象共用这一把锁
    • final:锁对象引用不变,不会换锁
  3. 绝对不要:
    • 不要用 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 全部打完→ 才打印主线程最后一句话

核心原理:

  1. 谁调用 join,就等谁死  :  A线程 里写 B.join()A 暂停,等待 B 线程全部执行完毕、死亡,A 才恢复运行

  2. 底层本质: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 条铁律

  1. 必须在 synchronized 同步代码 / 方法里调用  ,   不在锁里调用 → 直接抛 IllegalMonitorStateException

  2. 必须是 锁对象 调用例如:synchronized(lock) → 必须用 lock.wait()lock.notify()

  3. 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();
    }
}

执行流程

  1. A 抢到 CPU,拿到锁
  2. A 调用 wait()释放锁 + 让出 CPU
  3. B 抢到 CPU,顺利拿到锁
  4. B 调用 notify() 唤醒 A
  5. B 执行完同步代码块,释放锁
  6. A 被唤醒,回到就绪态,重新抢 CPU、抢锁,继续执行

四大线程方法对比

方法所属类是否让出 CPU是否释放锁线程状态唤醒条件
sleep()Thread让出不释放阻塞时间到
yield()Thread让出不释放就绪重新抢 CPU
join()Thread让出释放锁等待阻塞目标线程死亡
wait()Object让出释放锁等待阻塞notify/notifyAll

9、Java 线程安全类 & 非线程安全类

1. 非线程安全类(速度快,无锁)

代表:ArrayListHashMapStringBuilder

  • 内部没有任何锁

  • 多线程同时改,数据错乱、丢数据、炸异常

  • 执行效率极高

2. 线程安全类(慢,加锁保护)

代表:VectorHashtableStringBuffer

  • 内部自带 synchronized 锁

  • 多线程同时操作不会乱

  • 同一时间只有一个线程能改,性能差

10、什么是死锁

直白定义

多个线程,互相持有对方需要的锁,并且都不肯释放自己手里的锁,无限等待,程序卡死不动。

死锁必须同时满足 4 个必要条件(面试必背)

这四个缺一不可,只要破坏任意一个,死锁就不会发生。

  1. 互斥条件一把锁同一时间只能被一个线程持有,别人抢不到。(synchronized 天生满足)

  2. 请求保持条件线程已经拿着一把锁了,不释放手里的锁,又去申请别人的锁。

  3. 不可剥夺条件锁只能持有者主动释放,别人不能强行抢走锁。(synchronized 天生满足)

  4. 循环等待条件线程 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

然后程序永远卡住,不再输出任何内容

卡死原因拆解

  1. A 持有 lock1,等待 lock2

  2. B 持有 lock2,等待 lock1

  3. 互相等待,都不释放自己手里的锁

  4. 满足全部 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();
    }
}

运行现象

  1. 守护线程一直在无限循环打印

  2. 用户线程跑完 5 次结束

  3. 瞬间!JVM 退出,守护线程直接被杀死,循环强行终止程序直接结束,不会等守护线程跑完。

守护线程高频注意事项(面试重点)

  • 设置时机:setDaemon(true) 必须在start()之前,线程启动后无法修改守护状态

  • finally执行:守护线程被强制终止时,finally代码块不一定执行(可能来不及运行)

  • CPU与锁:守护线程仍会参与CPU抢占、锁竞争,也可能发生死锁(与普通线程规则一致)

  • 状态继承:父线程是守护线程,其创建的子线程默认也是守护线程

  • 禁用场景:禁止用于执行文件写入、数据库操作、资源关闭等任务(易导致数据损坏、资源泄露)

  • 常用场景:日志收集、心跳检测、缓存清理、JVM自带的GC(垃圾回收)线程

当守护线程正在执行任务时,若所有用户线程全部结束,JVM 会直接强制终止守护线程,此时守护线程中 finally 代码块可能来不及执行,并非一定能执行完成。

关键补充

  1. 正常用户线程:执行到 finally 代码块时,会确保执行完毕(除非程序异常终止);

  2. 守护线程:因 JVM 退出是 “强制终止”,不会等待守护线程的 finally 代码执行,可能导致 finally 中用于关闭资源、释放锁的代码未运行,进而引发资源泄露。


欢迎大家评论交流,一起加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值