本文的主要围绕着下面这个问题展开的,在阅读之前可以先自己思考一下问题的答案是什么?
- 一般操作系统的线程状态都有哪些?
- Java中的线程周期状态的生命周期状态都有哪些?
- Java中线程状态是如何转化的?
- Java中线程常用方法有哪些?
操作系统的线程状态
从操作系统的层面来说线程的状态划分为五种:初始状态、就绪状态、运行状态、阻塞状态和终止状态
初始状态(创建)
通过线程创建函数创建出来的新线程,在线程创建函数执行完后,将返回一个线程标识符供以后使用
就绪状态
操作系统中的线程被创建,可以分配CPU资源执行,但是还没有开始执行
运行状态
可运行状态的线程获得CPU资源以后,线程正在执行中,进入此状态
阻塞状态
指线程在执行中因某事件而受阻,处于暂停执行时的状态。此时线程会释放CPU资源,阻塞状态的线程没有机会获得CPU的使用权。
终止状态
线程执行完毕或者出现错误进入终止状态
Java中的线程周期状态
参考JDK18中java.lang.Thread.State
,Java线程的状态可以划分一下几种:NEW
,RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
,TERMINATED
New(新建)
只是新创建出来的线程,还没有调用start()
方法开始执行线程中的代码。
RUNNABLE(可运行)
这里的可运行状态(RUNNABLE)相当于操作系统线程状态中的就绪状态(READY)和运行状态(RUNNING)
抢占式调度系统会分配给就绪状态的线程一个时间片来执行任务。当时间片用完时,操作系统会根据优先级选择其他线程运行。一个线程在系统层面上可能是等待运行,也可能是正在运行。但是这些状态对JVM来讲,都可以看做可运行状态。
BLOCKED(阻塞)
该状态只与synchronized
锁相关,WAITING,TIMED_WAITING
状态下唤醒后因为需要竞争锁也会进入该状态
Java中的BLOCKED
状态与操作系统中的阻塞状态不同,Java中的阻塞一定跟锁有关系。
从操作系统来说,线程因为调用阻塞API(如IO操作)会进入阻塞状态,在JVM下这个线程会是什么状态呢?不知道有没有大佬解释一下。
查询相关资料解释如下:
对JVM来说,等待CPU使用权(操作系统中线程处于可执行状态)和等待IO操作(操作系统中的线程处于休眠状态)没有区别,都是在等待某个资源,都被JVM认为是
RUNNABLE
状态。所以是
RUNNABLE
状态?
WAITING(等待)
一个线程正在无期限等待另一个线程执行一个特定的动作唤醒此线程,被唤醒的线程会进入BLOCKED
状态,重新竞争锁。
TIMED_WAITING(计时等待)
超时等待,让出CPU,不会无期限等待被其他线程唤醒。时间到了可以自动唤醒
TERMINATED(终止)
线程已经终止,可能是正常终止,也可能是异常终止,一般可以终止的操作如下所示:
run()
方法执行结束- 线程执行抛出异常终止
- 对线程的实例调用
stop()
方法,现在该方法已经被废弃了。如果我们需要中断run()
方法,可以调用interrupt()
方法。
Java线程中的阻塞状态(BLOCKED)、无时限等待状态(WAITING)、有时限等待状态(TIMED_WAITING)都是一种状态,即通用线程生命周期中的休眠状态。也就是说,只要Java中的线程处于这三种状态时,那么,这个线程就没有CPU的使用权。
状态的转换
NEW到RUNNABLE状态
调用线程对象的start()
方法
RUNNABLE与BLOCKED的状态转换
RUNNABLE
转换为BLOCKED
只有一种可能:要进入synchronized修饰的方法、代码块,却因为获取不到锁标志,所以变成了阻塞。
RUNNABLE与WAITING状态转换
- 获得
synchronized
隐式锁的线程,调用无参的Object.wait()
方法 - 调用无参数的
Thread.join()
方法。例如,在线程A中调用线程B的join()
方法,则线程A会等待线程B执行完以后再继续执行。而线程A在等待线程B执行的过程中,其状态会从RUNNABLE
转换到WAITING
。当线程B执行完毕,线程A的状态则会从WAITING
状态转换成RUNNABLE
状态。 - 调用
LockSupport.park()
方法,当前线程会阻塞,线程的状态会从RUNNABLE
转换成WAITING
。调用LockSupport.unpark(Thread thread)
可唤醒目标线程,目标线程的状态又会从WAITING
状态转换到RUNNABLE
。
RUNNABLE与TIMED_WAITING状态转换
基本上都是调用带有超时参数的方法,如下所示:
- 调用带超时参数的
Thread.sleep(long millis)
方法; - 获得synchronized隐式锁的线程,调用带超时参数的
Object.wait(long timeout)
参数; - 调用带超时参数的
Thread.join(long millis)
方法; - 调用带超时参数的
LockSupport.parkNanos(Object blocker, long deadline)
方法; - 调用带超时参数的
LockSuppor.parkUntil(long deadline)
方法
RUNNABLE到TERMINATED状态
run()
方法执行结束- 线程执行抛出异常终止
- 对线程的实例调用
stop()
方法,现在该方法已经被废弃了。如果我们需要中断run()
方法,可以调用interrupt()
方法。
线程常见方法
Object类:wait(), notify(), notifyAll()
Thread类:start(), sleep(), yield(), join()
wait()方法
使用同步对象调用此方法,使当前线程处于等待状态,直到其他线程调用同步对象的notify()
方法或 notifyAll()
方法唤醒线程,或者超过设置的超时时间。
方法的两个参数:
- timeout - 等待时间(以毫秒为单位)
- nanos - 额外等待时间(以纳秒为单位)
方法有几个注意点:
- 超时时间为
timeout
与nanos
之和 timeout
与nanos
参数都为 0,则不会超时,等同于wait()
- 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
wait()
方法会释放对象的“锁标志”,失去CPU使用权- 调用
wait(),wait(0),wait(0,0)
方法后进入WAITIN
状态 - 有参方法调用后,此线程进入
TIMED_WAITING
状态
notify()方法
使用同步对象调用此方法,从对象等待池中随机选一个线程移出并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,在当前线程放弃对该对象的锁定之前,被唤醒的线程将无法继续,它们随时准备争夺锁的拥有权。
- 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
- 被唤醒的线程进入BLOCKED 状态,重新竞争锁
notifyAll()方法
使用同步对象调用此方法,唤醒对象等待池中所有的等待线程,让他们加入锁标志等待池中竞争锁。
- 调用方法前必须拥有对象的锁,否则会发生 IllegalMonitorStateException 异常
- 被唤醒的线程进入BLOCKED 状态,重新竞争锁
wait()
,notify()
及notifyAll()
只能在synchronized
语句中使用,但是如果使用的是ReenTrantLock
实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReenTrantLock.newCondition()
获取一个Condition
类对象,然后Condition
的await()
,signal()
以及signalAll()
分别对应上面的三个方法。
start()方法
启动线程,使用线程的实例调用此方法,JVM会调用此线程的run方法。
- 调用方法后进入
RUNNABLE
状态,失去CPU使用权 - 不能多次启动同一线程实例;线程一旦结束,也不能重新启动。两者都会抛出
java.lang.IllegalThreadStateException
异常
sleep(long millis)方法
JDK18 描述如下所示:
Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.
使当前执行的线程在指定的毫秒数内休眠(暂时停止执行),这取决于系统计时器和调度程序的精度和准确性。线程不会失去任何监视器的所有权。
- 这个方法需要传入参数,表示线程睡眠指定的时间
- 调用方法后进入
TIMED_WAITING
状态,失去CPU使用权 - 不会释放“锁标志”,如果有
synchronized
同步块,其他线程仍然不能访问共享数据 - 时间到了以后自动唤醒进入
RUNNABLE
状态
yield()方法
JDK18 描述如下所示:
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU. Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.
It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the
java.util.concurrent.locks
package.
此方法是一种尝试改变操作系统的线程调度的方法,调用此方法只是使当前线程重新回到可执行状态,该线程改变状态后可能会被马上执行。
- 调用方法后进入操作系统层面的就绪状态
- 不会释放“锁标志”
yield()
方法只能使同优先级或者高优先级的线程得到执行机会
join()方法
JDK18 描述如下所示:
Waits at most
millis
milliseconds for this thread to die. A timeout of0
means to wait forever.This implementation uses a loop of
this.wait
calls conditioned onthis.isAlive
. As a thread terminates thethis.notifyAll
method is invoked. It is recommended that applications not usewait
,notify
, ornotifyAll
onThread
instances.
A线程中调用B线程的join()方法,则A线程会等待B线程结束以后在继续执行。从源码实现以及jdk文档描述我们可以看出join
是基于wait
方法实现。
- 这个方法可以传入参数,参数为
0
时相当于无参调用 join()
和join(0)
相等,都是永远等待,调用方法后进入WAITIN
状态- 有参方法调用后,此线程进入
TIMED_WAITING
状态 - 会释放“锁标志”
- 对已经运行结束的线程调用
join()
方法会立刻返回