• Java线程的状态和状态转换


    一、线程的状态

    1.Java中的6种线程状态

    Java语言定义了6种线程状态

        public enum State {
            NEW,
            RUNNABLE,
            BLOCKED,
            WAITING,
            TIMED_WAITING,
            TERMINATED;
        }

    新建(New)

    线程创建后尚未启动。

    可运行(Runnable)

    一旦调用了start方法,线程就处于可运行状态。可运行状态的线程可能正在运行,也可能还没有运行而正在等待 CPU 时间片。(Java规范中并没有分为可运行状态和正在运行状态这两个状态,而是把它们合为一个状态。所以,可以把一个正在运行中的线程仍然称其处于可运行状态。)

    阻塞(Blocked)

    处于阻塞状态的线程并不会占用CPU资源。

    以下情况会让线程进入阻塞状态:

    ①等待获取锁

    等待获取一个锁,而该锁被其它线程持有,则该线程进入阻塞状态。当其它线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。

    ②IO阻塞

    线程发起了一个阻塞式IO后也会进入阻塞状态。最典型的场景,如等待用户输入内容然后继续执行。

    无限期等待(Waiting)

    处于这种状态的线程不会被分配 CPU 时间片,需要等待其它线程显式地唤醒。

    以下方法会让线程进入无限期的等待状态

    • Object.wait() 方法                    结束:Object.notify() / Object.notifyAll()
    • Thread.join() 方法                    结束:被调用的线程执行完毕
    • LockSupport.park() 方法          结束:LockSupport.unpark(currentThread)

    限时等待(Timed Waiting)

    处于这种状态的线程也不会被分配CPU 时间片,在一定时间之后会被系统自动唤醒。

    以下方法会让线程进入限期等待状态:

    • Thread.sleep(time) 方法          结束:sleep时间结束
    • Object.wait(time) 方法             结束:wait时间结束,或者Object.notify() / notifyAll()    
    • LockSupport.parkNanos(time)/parkUntil(time) 方法     结束:park时间结束,或者LockSupport.unpark(当前线程)

    死亡(Terminated)

    可以是线程结束任务之后自己结束,或者产生了异常而结束。

    注意:在有些书籍(如《Java并发编程实战》中5.4章节)或笼统的称呼中,就将阻塞、等待和超时等待这三种状态统称为阻塞状态。

    2.几种状态的转换

    二、等待、阻塞、中断、睡眠挂起

      描述 释放锁 cpu
    等待 线程因等待某个条件而进入等待状态 会释放锁 不会被分配CPU时间片
    阻塞 线程因竞争锁失败而进入阻塞状态 未获取到锁  
    睡眠 让出CPU的使用权让其它线程执行 不会释放锁 让出CPU的使用权
    挂起      
    yield 让出CPU的使用权,进入就绪状态(runnable)   让出CPU的使用权
    中断

    并非终止线程,而是给该线程发送一个中断通知,让其自行决定在合适的时间对中断通知做出响应(响应可以是终止线程,或者不做出响应)。

       
    join

    等待调用该方法的线程执行完毕后,线程再往下继续执行

       

    1.阻塞状态和等待状态的区别

    阻塞状态:等待获取锁或者IO阻塞。

    等待状态:因条件的不允许而暂停运行,会释放锁。

    更多的细节可以参考:线程同步机制 — 锁的内置锁的调度(监视器模型)这部分内容。

    2.wait和sleep的区别

    ①sleep来自Thread类,和wait来自Object类。

    ②sleep不会释放锁,而wait会释放了锁。

    ③sleep必须捕获异常,而wait/notify/notifyAll不需要捕获异常。

    ④wait/notify/notifyAll必须在同步方法或同步代码块中调用,而sleep没有这方面的限制。 (原因见本文末)

    3.interrupt

    线程中断并非线程终止,而是要给该线程发一个中断信号让它自己决定如何处理。

    实际上是设置了线程的中断标志位,在线程阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,重新复位为false,这样线程就得以退出阻塞的状态。

    关于interrupt的介绍可以参考博客:任务的取消 

    三、线程常见的方法

    1.sleep

    使当前线程休眠(暂停运行)指定的时间。

    用法:Thread.sleep(time)

    Thread.sleep(0)

    Thread.sleep(0)的作用是触发操作系统立刻重新进行一次CPU竞争。

    参考文章:代码中的Thread.sleep(0) 有什么意义?是写错了吗?

    2.join

    等待相应线程运行结束。若线程A调用线程B的join方法,那么线程A将会暂停运行,直到线程B运行结束。(谁join进来就等谁先运行完)

    join实际上是通过wait来实现的。

    3.yield

    使当前线程主动放弃对处理器的占用,这可能导致当前线程被暂停。

    这个方法是不可靠的,该方法被调用时当前线程可能仍然继续运行(视系统当前的运行状况而定)。

    4.wait和notify()/notifyAll()

    需要注意的是

    ①wait和notify/notifyAll方法只能在同步代码块里调用

    wait()、notify()、notifyAll() 这三个方法能够被调用的前提是已经获取了相应的互斥锁,所以我们会发现 wait()、notify()、notifyAll() 都是在synchronized{}内部被调用的

    ②notify操作必须要在wait操作之后,否则,可能导致线程不会醒来。

    ③wait

    5.LockSupport.park()/parkNano(time)/parkUntil(time) 

    LockSupport比Object的wait/notify有两大优势:

    ①LockSupport不需要在同步代码块里 。所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦。

    ②unpark方法可以先于park方法调用,所以不需要担心线程间的执行的先后顺序。

    四、线程已过时的方法

    stop

    官方的解释:Why is Thread.stop deprecated?

    stop被废弃的原因是stop太过于暴力,强行把线程终止,可能会引起数据的不一致性。

    suspend/resume

    suspend和resume是一对相反的操作,被suspend的线程必须要等到resume操作才能继续执行。

    suspend在挂起线程时并不会释放任务锁资源,其它线程想要访问被它持有的锁都会收到牵连而导致无法继续运行。

    而如果resume操作意外地在suspend之前先执行,那么被挂起的线程很难有机会被继续执行了。而且被挂起的线程从线程状态上看居然还是Runnable,这也会严重影响我们对系统当前状态的判断。

    五、关于wait的使用注意事项

    1.wait/notify/notifyAll必须在同步方法或同步代码块中调用

    wait/notify/notifyAll必须在同步方法或同步代码块中调用,否则会抛出IllegalMonitorStateException异常wait会释放锁,同时让线程等待。所以必须在同步方法或同步代码块中调用,表示已经获取了锁,然后才能释放锁。

    原问题出处:Why wait(), notify(), notifyAll() must be called inside a synchronized method/block?

    权威解释:Chapter 20 of Inside the Java Virtual Machine - Thread Synchronization by Bill Venners (《深入Java虚拟机》第二版第20章)

    中文翻译:为什么wait(),notify(),notifyAll()必须在同步方法/代码块中调用?

    2.wait为什么要在while循环,而非if中调用?

    使用wait时需要将其放在while循环中,而非使用if。我们通过下面的例子来分析!

    produce和consume方法用来模拟生产者和消费者方法。

    produce方法中,当队列未满时才进行生产放入队列,否则让线程等待。这里就需要注意了,当处于等待状态的线程被唤醒后是否就能立即生产呢?答案显然是否定的,因为多个线程在同时操作队列,当线程被唤醒后很可能有其它生产者线程又将队列填满了,所以只能让线程继续等待,而使用if显然是不能达到此目的,必须使用while,表示被唤醒后必须重新检查条件是否满足,只有满足条件后才能继续往下执行,否则只能继续等待。

    同理consume方法中也是一样,也必须将wait放在while中,表示消费者线程被唤醒后必须先检查条件是否满足。

        public void produce() {
            synchronized (this) {
                while (queue.isFull()) {//使用while,而非if
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
                queue.add("object");//生产
                notifyAll();
            }
        }
    
        public void consume() {
            synchronized (this) {
                while (queue.isEmpty()) {//使用while,而非if
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
                queue.remove();//消费
                notifyAll();
            }
        }

    常见问题汇总:

    1.java中线程的几种状态?状态间的转换?

    2.挂起和等待有什么区别?

    3.wait和sleep的区别?

    4.sleep(0)有什么含义?

    5.wait、notify/notifyAll为什么要在synchronized块中调用?

    6.wait为什么要在while循环而非if中调用?

    7.interrupt()和static interrupted()两者的区别?

    public void interrupt():中断线程

    public boolean interrupted():判断是否被中断

    public static boolean interrupted():判断是否被中断,并清除当前中断状态。

    8.stop,suspend/resume过时的原因?

  • 相关阅读:
    c++ 模板<template class T>
    HTML Agility Pack 搭配 ScrapySharp,彻底解除Html解析的痛苦
    用1年的经验做了10年还是,用10年的经验做一件事.
    last_inset_id()mysql注意
    小心变成这样一个人!!!
    主动哥
    转:开个小书店。。呵呵
    mysql 更改主键信息
    磁盘预录
    评估项目
  • 原文地址:https://www.cnblogs.com/rouqinglangzi/p/10803194.html
Copyright © 2020-2023  润新知