具体可参考:Java并发编程:Thread类的使用,这里对线程状态的转换及主要函数做一下补充。
一. 线程状态转换图
注意:
- 调用obj.wait()的线程需要先获取obj的monitor,wait()会释放obj的monitor并进入等待态。所以wait()/notify()都要与synchronized联用。详见:JAVA多线程之wait/notify
1.1 阻塞与等待的区别
阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。
等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。
需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,但被唤醒后又都进入runnable态,从行为效果来看又是一样的。
二. 主要操作
2.1 start()
新启一个线程执行其run()方法,一个线程只能start一次。主要是通过调用native start0()来实现。
1 public synchronized void start() {
2 //判断是否首次启动
3 if (threadStatus != 0)
4 throw new IllegalThreadStateException();
5
6 group.add(this);
7
8 boolean started = false;
9 try {
10 //启动线程
11 start0();
12 started = true;
13 } finally {
14 try {
15 if (!started) {
16 group.threadStartFailed(this);
17 }
18 } catch (Throwable ignore) {
19 /* do nothing. If start0 threw a Throwable then
20 it will be passed up the call stack */
21 }
22 }
23 }
24
25 private native void start0();
2.2 run()
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
2.3 sleep()
sleep方法有两个重载版本:
1 sleep(long millis) //参数为毫秒
2
3 sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
2.4 yield()
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
2.5 join()
join方法有三个重载版本:
1 join()
2 join(long millis) //参数为毫秒
3 join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
join()实际是利用了wait(),只不过它不用等待notify()/notifyAll(),且不受其影响。它结束的条件是:1)等待时间到;2)目标线程已经run完(通过isAlive()来判断)。
1 public final synchronized void join(long millis) throws InterruptedException {
2 long base = System.currentTimeMillis();
3 long now = 0;
4
5 if (millis < 0) {
6 throw new IllegalArgumentException("timeout value is negative");
7 }
8
9 //0则需要一直等到目标线程run完
10 if (millis == 0) {
11 while (isAlive()) {
12 wait(0);
13 }
14 } else {
15 //如果目标线程未run完且阻塞时间未到,那么调用线程会一直等待。
16 while (isAlive()) {
17 long delay = millis - now;
18 if (delay <= 0) {
19 break;
20 }
21 wait(delay);
22 now = System.currentTimeMillis() - base;
23 }
24 }
25 }
2.6 interrupt()
此操作会中断等待中的线程,并将线程的中断标志位置位。如果线程在运行态则不会受此影响。
可以通过以下三种方式来判断中断:
1)isInterrupted()
此方法只会读取线程的中断标志位,并不会重置。
2)interrupted()
此方法读取线程的中断标志位,并会重置。
3)throw InterruptException
抛出该异常的同时,会重置中断标志位。
2.7 suspend()/resume()
挂起线程,直到被resume,才会苏醒。
但调用suspend()的线程和调用resume()的线程,可能会因为争锁的问题而发生死锁,所以JDK 7开始已经不推荐使用了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)