Java之线程基础详情篇!

举报
喵手 发表于 2025/12/08 21:00:58 2025/12/08
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

一、线程生命周期与状态转换

Java 里线程的状态是 Thread.State 这个枚举定义的,常见的就六个:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

你可以把一个线程想象成人在公司上班的一天:刚入职、排队打卡、占用会议室、等别人开会、定时闹钟、最后下班走人。

1. NEW:刚创建,还没“开工”

Thread t = new Thread(() -> {
    System.out.println("running...");
});
System.out.println(t.getState()); // NEW
  • 只调用了 new Thread(...),还没 start()
  • 仅仅是个“Java 对象”,还没有真正对应到底层系统线程

2. RUNNABLE:正在 CPU 上排队 / 跑着

start() 之后,线程进入 RUNNABLE

t.start();
System.out.println(t.getState()); // 通常是 RUNNABLE

注意:

  • 在 Java 里,RUNNABLE 包含了 “就绪 + 运行中” 两种状态
  • 具体是正在 CPU 上跑,还是在就绪队列里等,JVM 不对你细分

典型进入 RUNNABLE 的路径:

  • NEW 调用 start()
  • BLOCKED 拿到锁
  • WAITING / TIMED_WAITING 被唤醒且拿到锁
  • 从 I/O 阻塞恢复(某些实现会表现为 RUNNABLE)

3. BLOCKED:等着进“临界区”的锁

BLOCKED 专门表示:

线程在等待一个 synchronized 的监视器锁(intrinsic lock)。

例如:

final Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(100000); } catch (InterruptedException e) {}
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 got lock");
    }
});

t1.start();
Thread.sleep(100); // 让 t1 先拿到锁
t2.start();
Thread.sleep(100);
System.out.println(t2.getState()); // 可能是 BLOCKED

这里:

  • t1 先进入 synchronized 块,并 sleep,一直占着锁
  • t2 想进 synchronized 块,发现锁被占用 → BLOCKED
  • t1 出 synchronized,t2 才能从 BLOCKEDRUNNABLE

4. WAITING:无限期等待某个条件

WAITING 表示:

线程在无限期等待,直到被别的线程显式唤醒。

常见导致 WAITING 的方法:

  • Object.wait()(无超时)
  • Thread.join()(无超时)
  • LockSupport.park()

例子(wait / notify):

final Object lock = new Object();

Thread t = new Thread(() -> {
    synchronized (lock) {
        try {
            lock.wait();  // 线程进入 WAITING,并且释放锁
            System.out.println("被唤醒啦");
        } catch (InterruptedException e) {}
    }
});

t.start();
Thread.sleep(100);
System.out.println(t.getState()); // WAITING

synchronized (lock) {
    lock.notify();
}

特点:

  • 线程会释放锁(和 BLOCKED 不同)
  • 必须等到别人 notify() / notifyAll() / interrupt 才可能继续

5. TIMED_WAITING:带闹钟的等待

TIMED_WAITING 表示:

线程在有“时间上限”的等待:时间到了会自动醒。

典型来源:

  • Thread.sleep(millis)
  • Object.wait(timeout)
  • 带超时的 Thread.join(timeout)
  • LockSupport.parkNanos() / parkUntil()

例子:

Thread t = new Thread(() -> {
    try {
        Thread.sleep(10000); // 10 秒
    } catch (InterruptedException e) {}
});

t.start();
Thread.sleep(100);
System.out.println(t.getState()); // TIMED_WAITING
  • 超时到了,线程从 TIMED_WAITINGRUNNABLE
  • 如果是 wait(timeout),还需要重新拿锁才能继续执行

6. TERMINATED:线程结束了

run()call() 执行完,线程就进入 TERMINATED

Thread t = new Thread(() -> {
    System.out.println("done");
});
t.start();
t.join(); // 等它结束
System.out.println(t.getState()); // TERMINATED

7. 状态转换小总结(文字版“状态图”)

可以把线程的一生理解为:

  • NEW →(start)→ RUNNABLE

  • RUNNABLE

    • 进入 synchronized 前抢锁失败 → BLOCKED
    • 调用 wait() / join() / park()WAITING
    • 调用 sleep() / wait(timeout) / join(timeout) / parkNanosTIMED_WAITING
  • BLOCKED / WAITING / TIMED_WAITING

    • 拿到锁、被唤醒、超时、被中断 等 → 回到 RUNNABLE
  • 最终,run 执行完 → TERMINATED

二、Thread vs Runnable vs Callable:谁负责干活?谁只是“任务说明书”?

你可以这么理解:

  • Runnable / Callable:任务说明书
  • Thread:真正的“工人”
  • 线程池(ExecutorService):工头 + 员工队列

1. Thread:线程本体 + 可选“任务”

Thread 是一个类,代表一个线程对象,它内部有一个 run() 方法:

  • 可以直接继承 Thread,重写 run()
  • 也可以把 Runnable 丢进去,让它帮你跑

示例 1:继承 Thread(不太推荐)

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("I am a Thread");
    }
}

new MyThread().start();

问题:类已经被占用,不能再继承其它父类,可扩展性较差。

2. Runnable:没有返回值的“任务接口”

Runnable 是一个函数式接口,只定义了一个 void run()

Runnable task = () -> {
    System.out.println("running in " + Thread.currentThread().getName());
};

new Thread(task).start();

特点:

  • 没有返回值
  • 不能抛 checked exception(只能自己 try-catch)
  • 适合那些只需要“干完就完了”的任务

在使用线程池时,execute(Runnable command) 就是跑这种任务。

3. Callable:有返回值的任务接口(配合 Future)

Callable<V> 是比 Runnable 更“高级”一点的任务描述:

  • 方法是 V call() throws Exception;
  • 有返回值
  • 可以抛 checked exception

典型用法:配合 ExecutorService.submit()Future 使用:

import java.util.concurrent.*;

Callable<Integer> task = () -> {
    Thread.sleep(1000);
    return 42;
};

ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(task);

System.out.println("doing something else...");
Integer result = future.get();   // 阻塞等待结果
System.out.println("result = " + result);

pool.shutdown();

对比一下:

对象 类型 核心方法 返回值 是否能抛 checked 异常 常见使用场景
Thread run() / start() void 不支持(run 不能抛) 直接表示一个线程
Runnable 函数式接口 run() void 不支持 提交到线程池、给 Thread 跑
Callable 函数式接口 call() 任意类型 V 支持 有返回值、有异常的异步任务

一句直白总结:

  • 想要返回值?用 Callable + Future
  • 不需要返回值,只是跑一下逻辑?Runnable 就够了。
  • 不要随便 new Thread,优先线程池。

三、sleep() vs wait():都“睡觉”,但一个只是打盹,一个是“放弃座位排队重来”

这俩名字都很迷惑新人,但本质区别非常关键。

1. 来个对比大表先压压惊

对比点 Thread.sleep() Object.wait()
所在类 Thread Object
是否释放锁 ❌ 不释放锁 ✅ 释放对象监视器锁(synchronized 的那个锁)
是否必须在 synchronized 里调用 ❌ 不要求 ✅ 必须持有该对象的 monitor,否则抛 IllegalMonitorStateException
唤醒方式 时间到了 / 被中断 notify() / notifyAll() / 时间到 / 被中断
典型用途 单纯让线程暂停一段时间,不用于通信 线程间协作 / 条件等待(生产者-消费者等)
所属状态 通常进入 TIMED_WAITING WAITINGTIMED_WAITING

2. sleep():谁也不管,我就静静躺一会儿

try {
    Thread.sleep(1000); // 当前线程睡 1 秒
} catch (InterruptedException e) {
    // 响应中断
}

特点:

  1. 当前线程进入 TIMED_WAITING
  2. 不会释放已经占有的任何锁(比如你在 synchronized 里 sleep)
  3. 时间到后自动恢复到 RUNNABLE
  4. 中途被 interrupt() 会抛 InterruptedException,并清除中断标志

比如:

synchronized (lock) {
    Thread.sleep(10000); // 期间别人拿不到这个 lock
}

这就是为什么在 synchronized 里长时间 sleep() 是个风险操作:
你拿着锁睡觉,别人全被堵在外面。

3. wait():把锁先让出来,等别人叫我再继续

wait() 是配合 synchronized 使用的线程协作机制

final Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("t1: wait before");
            lock.wait(); // 1. 释放 lock
            System.out.println("t1: resumed");
        } catch (InterruptedException e) {}
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2: notify");
        lock.notify();
    }
});

关键行为:

  1. 调用 lock.wait() 前,当前线程必须已经持有 lock 的锁(在 synchronized 块/方法中)

  2. 调用 wait() 后:

    • 当前线程进入 WAITINGTIMED_WAITING
    • 释放该对象的锁,让别的线程可以拿到这个锁干活
  3. 其他线程在同一个对象锁上调用 notify() / notifyAll() 后:

    • 等待中的线程会被唤醒,重新去抢这个锁
    • 抢到锁之后,从 wait() 之后继续执行

4. 一个经典生产者-消费者对比示例

使用 wait() / notify() 实现简单阻塞队列

class MyBlockingQueue {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int capacity;

    public MyBlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(int value) throws InterruptedException {
        synchronized (queue) {
            while (queue.size() == capacity) {
                queue.wait(); // 队列满了,释放锁,等待消费者消费
            }
            queue.add(value);
            queue.notifyAll(); // 通知可能在等的消费者
        }
    }

    public int take() throws InterruptedException {
        synchronized (queue) {
            while (queue.isEmpty()) {
                queue.wait(); // 队列空了,释放锁,等生产者放数据
            }
            int val = queue.poll();
            queue.notifyAll(); // 通知可能在等的生产者
            return val;
        }
    }
}

这里用的是典型的 条件等待 + 释放锁 的模式,sleep() 完全干不了这事。

如果你用 sleep() 来轮询队列是否为空,不仅浪费 CPU(频繁检查),还不精确(时间控制难)。

5. sleep() vs wait() 再来几句“人话”版总结

  • sleep()

    “我就在这儿等一会儿,谁也别动我的东西(锁),时间到了我自己继续跑。”

  • wait()

    “我先把座位(锁)让出来,大家先忙,等你们叫我(notify)我再抢座回来继续干。”

所以:

  • 想要简单延时sleep()
  • 想要线程之间协作 / 条件同步wait() / notify()(或更现代的 Lock / Condition / BlockingQueue 等)

四、最后帮你串一下三块知识

如果把刚才三块揉成一个脑图:

  • 线程状态

    • 启动:NEW → RUNNABLE
    • 等锁:BLOCKED(synchronized)
    • 等条件 / 等别人叫:WAITING / TIMED_WAITING(wait/join/park/sleep)
    • 结束:TERMINATED
  • 任务 vs 线程

    • Runnable / Callable = 任务描述
    • Thread = 真正线程
    • 线程池 = 管线程 + 执行任务
  • sleep vs wait

    • 都会让线程进入 TIMED_WAITING
    • 最大差别:是否释放锁 & 是否需要别人“叫醒”
    • sleep:不管别人,就自己暂停
    • wait:用于线程协调,释放锁,配合 notify/notifyAll

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。