多线程开发基础总结(一)—— java中线程的状态以及状态转换

java中线程的状态以及状态转换(详解)

前言

在进行多线程开发之前,一定要先了解的就是线程的状态的各个状态的情况以及状态之间的转化。网上关于线程状态的说法大概有三个版本,实际上都没有太大的问题,因为有的是从操作系统上去对线程状态进行划分,而有的是根据java源码来划分,所以会有一种情况就是你在这边看到线程有这几种状态,但是在另一边,则变成了另外的解释。并且,中间也有很多写的并不详细,也有一部分虽然没有大问题,但是中间混淆了概念。那么,这种情况下,看源码是最好的选择

java关于线程状态的源码

java中用了一个枚举类来表示线程的状态,分别是NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。

附上源码:

 public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
初始状态(New)

用了new语句创建后但并未启动的线程就处于初始的状态,此时,和java其他所有的对象调用new语句一样,只是分配了内存,并对成员变量进行初始化,但是此时的线程对象并没有展示线程的任何动态特性;而从源码注释上也知道,官方对这个状态的定义是:尚未启动的线程的线程状态,这个状态基本没什么异议

就绪状态(操作系统的Ready状态)

线程处于初始状态,然后调用了线程的start方法,根据需要进行资源竞争,比如进入锁池中,参与锁的竞争;而在获得资源之后,并不能马上进入执行的状态,因为还需要时间片的分配;只有被线程调度器选中,分配了时间片,才能进入运行状态;而在一个线程获得锁之后,获得时间片之前,称为就绪状态(操作系统的Ready状态);但是,从java源码中可以看到并没有单独的就绪状态,需要注意的是,调用start方法之后默认的线程就进入就绪状态,JVM会为该线程创建方法并且调用堆栈程序计数器,往往start之后的线程并不会立即执行,但是其实通过对线程进行操作让它立刻执行,比如设置其睡眠一毫秒,这里关于start方法等待的内容稍后再说;此处还需要谨记的一点是,只能对处于新建状态的线程调用start方法,否则会报错!

运行状态(操作系统中的Running状态)

当线程处于Ready状态之后,此时所有处于此状态的线程存在于一个线程池中,等待CPU调度选中,分配时间片;当线程分配到时间片之后,线程占用cpu,执行程序代码,此时,线程便处于一个运行的状态,这就是操作系统中的Running状态。但是,java源码中并不存在单独的Running运行状态。

RUNNABLE状态(可运行)

其实很多人也会把操作系统的Ready状态叫做可运行状态,其实这个叫法也没问题。上面讲到,java中并没有单独的就绪状态和运行状态,取而代之的是一个叫做RUNNABLE的状态,在Java源码的注释中可以看到,官方对于RUNNABLE的定义是 :A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
意思是 处于runnable状态的线程在JVM中执行,但也可能处于一个等待操作系统其他资源例如处理器(时间片)的状态。所以其实这里的可运行状态包含了操作系统中的Ready状态和Running状态 联系上面的我们可以知道的一点就是,在java中,线程新建之后调用start方法,便进入了所谓的Runnable状态。

BLOCKED(阻塞状态)

BLOCKED我把它直译过来就是阻塞状态,其实这样并不严谨,操作系统上对于阻塞状态的定义是指线程因为某些原因放弃CPU,暂时停止运行,而被阻塞的线程也会在合适的机会重新进入就绪状态。在我看来,完整看完官方对于BLOCKED、WAITING和TIMED_WAITING,我感觉java中更像是将阻塞状态细分成了这三种(个人理解)。

这是官方对于阻塞状态的解释:A thread in the blocked state is waiting for a monitor lock
to enter a synchronized block/method or
reenter a synchronized block/method after calling
{@link Object#wait() Object.wait}
.

大概意思是说等待获取一个对象锁(同步锁/也叫同步监视器)来进入一个同步块/同步方法,或者 调用了wait方法之后释放了本身所持有的锁然后想重新进入一个同步块/方法中。而操作系统上对于阻塞状态的定义是指线程因为某些原因放弃CPU,暂时停止运行,而被阻塞的线程也会在合适的机会重新进入就绪状态。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。

这部分网上有人讲BLOCKED是锁池状态,把BLOCKED叫锁池其实有误导的成分,看了他们的关系转换图,其实有些是挺有问题的,很多人写着写着就和JVM的一些概念混在一起了,我个人觉得,从源码上来看,处于BLOCKED状态的核心是在等待接受一个监视器(排它锁)让它来进入同步块,从这来看倒是确实很像lock pool(这才是锁池)的概念,但是,java并不存在锁池状态的这样一个概念,锁池只是存放那些处于等待获取被其他线程占用了的对象的同步锁的线程的地方(有点拗口,好好断句会理解的吧),群殴自己理解就是存放那些处于BLOCKED状态的线程的地方;总之,从源码上来看,BLOCKED关键点在于 waiting for a monitor lock to enter a synchronized block

WAITING(无限期等待)

直接看源码:A thread is in the waiting state due to calling one of the following methods:
{@link Object#wait() Object.wait} with no timeout
{@link #join() Thread.join} with no timeout
{@link LockSupport#park() LockSupport.park}
A thread in the waiting state is waiting for another thread to
perform a particular action

大概意思是一个线程进行了以下三种操作中的一种就属于无限期等待状态,这三种操作分别是:

  1. 调用没有设置 Timeout 参数的 Object.wait() 方法
  2. 调用没有设置 Timeout 参数的 Thread.join() 方法
  3. 调用LockSupport.park() 方法;

并且,处于WAITING状态的线程会等待另一个线程进行特定操作才会退出WAITING状态。比如:

如果因为调用了wait方法造成了处于WAITING状态的话,那么等待另一个线程调用Object.notify() / Object.notifyAll()后便可以退出这种状态

如果是因为调用join方法,那么等待被调用的线程执行完毕即可退出

调用LockSupport.park()造成的则等待其他线程调用LockSupport.unpark(Thread)进行唤醒。

需要注意的是,处于无限期等待状态的线程,需要等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

TIMED_WAITING(定时等待)

从源码可以知道,处于定时等待状态的原因可能是:

  1. 因为调用了Thread.sleep() 方法
  2. 可能是调用设置了 Timeout 参数的 Object.wait() 方法
  3. 可能是调用设置了 Timeout 参数的Thread.join() 方法
  4. 还有就是可能调用了LockSupport.parkNanos() 方法
  5. 调用了LockSupport.parkUntil() 方法

和WAITING状态不一样的是,限时等待无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

而退出定时等待状态的方法与上面对应的分别是:

  1. 时间结束
  2. 时间结束或者调用 Object.notify() / Object.notifyAll()
  3. 时间结束 或者 被调用的线程执行完毕
  4. 调用LockSupport.unpark(Thread)
  5. 调用LockSupport.unpark(Thread)

上面也讲到,我个人理解的是JAVA把阻塞状态细化成了以上三种,而把这三种总结起来大概可以把造成阻塞状态的比较常见的原因分为:

  1. 线程试图调用一个同步监视器,但是这个监视器被其他线程持有
  2. 线程调用了阻塞IO方法,在该方法返回之前,这个线程都处于阻塞状态
  3. 线程调用sleep方法,放弃占有的处理器资源(需注意的点是和调用wait方法有区别,sleep是不会释放持有的锁,而wait会)
  4. 线程等待某种通知(notify/notifyAll)这里也有要注意的点,就是应用层线程实例尽量不要使用notifyall、notify和wait方法,这两个是object方法,他们是系统调用的,如果你在线程实例上使用这几个方法,可能不一定会按照你想要的结果来工作,这些之后再来详细讲
  5. 程序调用了线程的suspend()方法(线程挂起)这个也有需要注意的地方,suspeng()方法在源码中可以看到已经是过时方法,意思是不推荐使用,原因suspend()不会释放锁,如果在resume方法之前发生加锁行为,那么就会造成死锁的情况,出现线程被冻结的情况;这个也以后再细讲。
  6. 调用了join()方法,这个其实不应该单独成为一个原因,因为实质上调用join方法会判断当前执行的线程是否执行结束,执行线程如果结束,那么这个线程会调用notifyall方法,然后结束等待;实际上属于第4类,但是上面没讲到,就还是把它单独拿出来讲一下

上面这些是我对java线程阻塞的理解的总结,而且综合起来,会发现这个也比较符合操作系统中对于线程阻塞状态的定义。

TERMINATED(终止、结束、死亡)

对于这个,还是比较统一的,我也总结了线程结束的几种情况:

  1. 是线程结束任务之后正常结束,比如run()方法和call()方法执行正常完成之后正常死亡
  2. 抛出了一个未捕获的异常或者error而结束
  3. 直接调用了stop()方法,但是这个从源码中也可以看到这个不推荐使用,因为它很暴力,线程可能当时正在执行,此时直接调用stop方法,线程会释放所有的锁,可能会出现脏数据,破环数据一致性,并且很难发现(不会报错)

结束语

其实我之前并没打算单独写一篇这么长的文章来单独写这个,而且最近写了几篇学习总结,还没整理好,本来是打算先写好基础的总结,顺便写一下多线程开发中很多基础方法的介绍,对于线程状态的我是打算copy一下,然后一笔带过就够了,但是再进行学习的过程中,发现很多人可能和我一样的想法,并没深入研究或者研究了没详细解释也是直接一笔带过了,并且很多的还存在一些误解(我认为的),所以我还是决定花点时间来详细写一下这个,还差一张状态转换图,我之后再补上吧,当然,如果你觉得我所讲的有问题,欢迎评论纠正我,共同学习,一起进步!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值