全文目录:
- 开篇语
- 一、线程生命周期与状态转换
- 1. NEW:刚创建,还没“开工”
- 2. RUNNABLE:正在 CPU 上排队 / 跑着
- 3. BLOCKED:等着进“临界区”的锁
- 4. WAITING:无限期等待某个条件
- 5. TIMED_WAITING:带闹钟的等待
- 6. TERMINATED:线程结束了
- 7. 状态转换小总结(文字版“状态图”)
- 二、Thread vs Runnable vs Callable:谁负责干活?谁只是“任务说明书”?
- 1. Thread:线程本体 + 可选“任务”
- 2. Runnable:没有返回值的“任务接口”
- 3. Callable:有返回值的任务接口(配合 Future)
- 三、sleep() vs wait():都“睡觉”,但一个只是打盹,一个是“放弃座位排队重来”
- 1. 来个对比大表先压压惊
- 2. sleep():谁也不管,我就静静躺一会儿
- 3. wait():把锁先让出来,等别人叫我再继续
- 4. 一个经典生产者-消费者对比示例
- 使用 `wait()` / `notify()` 实现简单阻塞队列
- 5. sleep() vs wait() 再来几句“人话”版总结
- 四、最后帮你串一下三块知识
- 文末
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
一、线程生命周期与状态转换
Java 里线程的状态是Thread.State这个枚举定义的,常见的就六个:
NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
你可以把一个线程想象成人在公司上班的一天:刚入职、排队打卡、占用会议室、等别人开会、定时闹钟、最后下班走人。
1. NEW:刚创建,还没“开工”
Threadt=newThread(()->{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)。
例如:
finalObjectlock=newObject();Threadt1=newThread(()->{synchronized(lock){try{Thread.sleep(100000);}catch(InterruptedExceptione){}}});Threadt2=newThread(()->{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才能从BLOCKED→RUNNABLE
4. WAITING:无限期等待某个条件
WAITING表示:
线程在无限期等待,直到被别的线程显式唤醒。
常见导致WAITING的方法:
Object.wait()(无超时)Thread.join()(无超时)LockSupport.park()
例子(wait / notify):
finalObjectlock=newObject();Threadt=newThread(()->{synchronized(lock){try{lock.wait();// 线程进入 WAITING,并且释放锁System.out.println("被唤醒啦");}catch(InterruptedExceptione){}}});t.start();Thread.sleep(100);System.out.println(t.getState());// WAITINGsynchronized(lock){lock.notify();}特点:
- 线程会释放锁(和
BLOCKED不同) - 必须等到别人
notify()/notifyAll()/ interrupt 才可能继续
5. TIMED_WAITING:带闹钟的等待
TIMED_WAITING表示:
线程在有“时间上限”的等待:时间到了会自动醒。
典型来源:
Thread.sleep(millis)Object.wait(timeout)- 带超时的
Thread.join(timeout) LockSupport.parkNanos()/parkUntil()
例子:
Threadt=newThread(()->{try{Thread.sleep(10000);// 10 秒}catch(InterruptedExceptione){}});t.start();Thread.sleep(100);System.out.println(t.getState());// TIMED_WAITING- 超时到了,线程从
TIMED_WAITING→RUNNABLE - 如果是
wait(timeout),还需要重新拿锁才能继续执行
6. TERMINATED:线程结束了
当run()或call()执行完,线程就进入TERMINATED:
Threadt=newThread(()->{System.out.println("done");});t.start();t.join();// 等它结束System.out.println(t.getState());// TERMINATED7. 状态转换小总结(文字版“状态图”)
可以把线程的一生理解为:
NEW→(start)→RUNNABLERUNNABLE→- 进入
synchronized前抢锁失败 →BLOCKED - 调用
wait()/join()/park()→WAITING - 调用
sleep()/wait(timeout)/join(timeout)/parkNanos→TIMED_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(不太推荐)
classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("I am a Thread");}}newMyThread().start();问题:类已经被占用,不能再继承其它父类,可扩展性较差。
2. Runnable:没有返回值的“任务接口”
Runnable是一个函数式接口,只定义了一个void run():
Runnabletask=()->{System.out.println("running in "+Thread.currentThread().getName());};newThread(task).start();特点:
- 没有返回值
- 不能抛 checked exception(只能自己 try-catch)
- 适合那些只需要“干完就完了”的任务
在使用线程池时,execute(Runnable command)就是跑这种任务。
3. Callable:有返回值的任务接口(配合 Future)
Callable<V>是比Runnable更“高级”一点的任务描述:
- 方法是
V call() throws Exception; - 有返回值
- 可以抛 checked exception
典型用法:配合ExecutorService.submit()和Future使用:
importjava.util.concurrent.*;Callable<Integer>task=()->{Thread.sleep(1000);return42;};ExecutorServicepool=Executors.newFixedThreadPool(2);Future<Integer>future=pool.submit(task);System.out.println("doing something else...");Integerresult=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 | WAITING或TIMED_WAITING |
2. sleep():谁也不管,我就静静躺一会儿
try{Thread.sleep(1000);// 当前线程睡 1 秒}catch(InterruptedExceptione){// 响应中断}特点:
- 当前线程进入
TIMED_WAITING - 不会释放已经占有的任何锁(比如你在 synchronized 里 sleep)
- 时间到后自动恢复到
RUNNABLE - 中途被
interrupt()会抛InterruptedException,并清除中断标志
比如:
synchronized(lock){Thread.sleep(10000);// 期间别人拿不到这个 lock}这就是为什么在synchronized里长时间sleep()是个风险操作:
你拿着锁睡觉,别人全被堵在外面。
3. wait():把锁先让出来,等别人叫我再继续
wait()是配合 synchronized 使用的线程协作机制。
finalObjectlock=newObject();Threadt1=newThread(()->{synchronized(lock){try{System.out.println("t1: wait before");lock.wait();// 1. 释放 lockSystem.out.println("t1: resumed");}catch(InterruptedExceptione){}}});Threadt2=newThread(()->{synchronized(lock){System.out.println("t2: notify");lock.notify();}});关键行为:
调用
lock.wait()前,当前线程必须已经持有lock的锁(在 synchronized 块/方法中)调用
wait()后:- 当前线程进入
WAITING或TIMED_WAITING - 释放该对象的锁,让别的线程可以拿到这个锁干活
- 当前线程进入
其他线程在同一个对象锁上调用
notify()/notifyAll()后:- 等待中的线程会被唤醒,重新去抢这个锁
- 抢到锁之后,从
wait()之后继续执行
4. 一个经典生产者-消费者对比示例
使用wait()/notify()实现简单阻塞队列
classMyBlockingQueue{privatefinalQueue<Integer>queue=newLinkedList<>();privatefinalintcapacity;publicMyBlockingQueue(intcapacity){this.capacity=capacity;}publicvoidput(intvalue)throwsInterruptedException{synchronized(queue){while(queue.size()==capacity){queue.wait();// 队列满了,释放锁,等待消费者消费}queue.add(value);queue.notifyAll();// 通知可能在等的消费者}}publicinttake()throwsInterruptedException{synchronized(queue){while(queue.isEmpty()){queue.wait();// 队列空了,释放锁,等生产者放数据}intval=queue.poll();queue.notifyAll();// 通知可能在等的生产者returnval;}}}这里用的是典型的条件等待 + 释放锁的模式,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 !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!