Java线程状态及转换

1. 线程状态及转换

1.1 六种线程状态

Java的Thread.State内部类中定义了6种线程状态,一个线程在任意时刻只能对应一种状态,可以通过特定方法进行状态切换。下面对这6种状态进行介绍:
(1)新建(NEW):创建后尚未启动的线程处于新建状态。
(2)可运行(RUNNABLE):RUNNABLE又分为READY和RUNNING两种状态,即线程可能在等待操作系统分配执行时间,也可能正在运行。
(3)无限期等待(WAITING):此状态下的线程会释放锁,不会被分配处理器执行时间,它们要等待被其他线程显示唤醒。当线程调用以下方法时,会使其切换到此状态:

  • Object.wait()或Object.wait(0)
  • Thread.join()或Thread.join(0)
  • LockSupport.park()

(4)限期等待(TIMED_WAITING):此状态下的线程同样不会被分配处理器执行时间,不过这类仍持有锁,无需等待被其他线程显式唤醒,在一定时间后会由系统自动唤醒。也可以被显式唤醒。当线程调用以下方法时,会使其切换到此状态:

  • Thread.sleep(millis),millis >= 0
  • Object.wait(millis),millis > 0
  • Thread.join(millis),millis > 0
  • LockSupport.parkNanos(nanos)
  • LockSupport.parkUtil(deadline)

(5)阻塞(BLOCKED):此状态下,线程会竞争锁对象,来进入同步方法或同步代码块。WAITING状态下的线程未持有锁,因此被唤醒后,会进入BLOCKED状态重新竞争锁;TIMED_WAITING状态下的线程本身就持有锁,因此等待指定时间后,会进入RUNNABLE状态。
(6)终止(TERMINATED):线程执行结束后处于终止状态。
JVM为锁对象维护了两个集合:_EntryList和_WaitSet,处于BLOCKED状态的线程被记录在_EntryList中,处于WAITING状态的线程被记录在_WaitSet中。

1.2 六种线程状态的相互转换

2. 线程状态切换的相关方法

2.1 Object中线程状态切换的相关方法
2.1.1 wait

(1)wait()

    // 调用wait相当于调用wait(0)
    public final void wait() throws InterruptedException {
        wait(0);
    }

(2)wait(long, int)

    public final void wait(long timeout, int nanos) throws InterruptedException {
        // 范围检查(略)
        if (nanos > 0) { // 纳秒数大于0则毫秒数加1
            timeout++;
        }
        // 调用wait(int)
        wait(timeout);
    }

(3)wait(long)

    // native方法
    public final native void wait(long timeout) throws InterruptedException;

从代码中可知,wait()和wait(long,int)最终都会调用wait(long),wait方法的特点如下:

  • wait方法只能在同步代码块或同步方法中被锁对象调用,否则会抛异常。任意时刻只能有一个线程拥有锁对象。
  • 当调用wait()或wait(0)时,线程会释放锁,并被加入到Wait Set中,Wait Set中的线程通过notify、notifyAll、Thread.intinterrupt被唤醒
  • 当调用wait(millis)(millis>0)时,线程不会释放锁,这类线程通过notify、notifyAll、Thread.intinterrupt被唤醒,当超过指定时间时也会被自动唤醒
2.1.2 notify和notifyAll

(1)notify

    // native方法
    public final native void notify();

notify的特点:

  • 每次只会唤醒一个处于WAITING状态的线程,使该线程从WAITING状态转为BLOCKED状态
  • 具体的唤醒顺序由实现决定,HotSpot实现中,调用notify方法时,以FIFO的顺序进行唤醒(每次唤醒一个),调用notifyAll方法时,以FILO的顺序进行唤醒(唤醒所有)
  • 被唤醒的线程在当前唤醒它的线程执行结束后,会从Wait Set中转移到Entry Set中,即从WAITING状态转换为BLOCKED状态,与其他处于BLOCKED状态的线程共同参与锁的竞争
  • 同wait一样,notify只能在同步代码块或同步方法中被调用

(2)notifyAll

    // native方法
    public final native void notifyAll();

notifyAll的特点与notify类似,区别:

  • notifyAll会唤醒所有处于WAITING状态的线程
  • 唤醒顺序与notify不同(如上面notify特点的第2条所述)
2.2 Thead中线程状态切换的相关方法
2.2.1 start
    // start用来启动一个线程。一个线程只能调用一次start方法。
    public synchronized void start() {
        // 该条件可保证一个线程只能启动一次
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        // try/catch(略)
        // 启动线程,底层会创建操作系统线程,经过一
        // 系列回调,最终会调用到该线程的run方法
        start0(); 
        started = true;
    }
2.2.2 run
    // 传入的Runnable实例对象
    private Runnable target;
    // 调用start方法后,底层会创建操作系统线程,
    // 经过一系列回调,最终会调用到该线程的run方法
    public void run() {
        // 调用Runnable实例对象的run方法
        if (target != null) {
            target.run();
        }
    }
2.2.3 sleep

(1)sleep(long,int)

    public static void sleep(long millis, int nanos)
    throws InterruptedException {
        // 范围检查(略)
        // 满足这俩条件中的一个时millis加1
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
        // 调用sleep(long)
        sleep(millis);
    }

(2)sleep(long)

    // 静态native方法
    // 使当前线程让出CPU,睡眠一段时间,但不会释放锁
    public static native void sleep(long millis) throws InterruptedException;

(3)sleep案例

public class TestSleep {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("t开始...");
                    // 使当前线程睡眠1秒
                    Thread.sleep(1000);
                    System.out.println("t结束...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        try {
            System.out.println("main开始...");
            // 注意下面三个sleep方法都会使主线程睡眠1秒
            t.sleep(1000);
            Thread.sleep(1000);
            Thread.currentThread().sleep(1000);
            System.out.println("main结束...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
2.2.4 join

(1)join()

    // 调用join()相当于调用join(0)
    public final void join() throws InterruptedException {
        join(0);
    }

(2)join(long, int)

    // 与sleep(long,int)类似
    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {
        // 范围检查(略)
        // 满足这俩条件中的一个时millis加1
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
        // 调用join(long)
        join(millis);
    }

(3)join(long)

    // 注意join被synchronized修饰,是同步方法,因此其中能调用wait方法
    // 此时的锁对象是this,即当前线程对象在当前线程终止时,
    // 会调用this.notifyAll唤醒调用了this.wait的线程(后面有例子)
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        // millis不能为负数
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        // millis为0,即通过join()、join(0)到这里
        if (millis == 0) {
            while (isAlive()) {
                // 相当于this.wait(0)
                wait(0); // 无限期等待
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                // 当isAlive()为true,但指定的毫秒数已到时,通过break跳出循环
                if (delay <= 0) {
                    break;
                }
                // 相当于this.wait(delay)
                // 限期等待
                // 可能是达到delay时间自动被唤醒,也可能是未达到delay时间被this.notifyAll唤醒
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    // native方法
    // 用来检测线程是否活着,即线程是否已经启动且尚未终止
    public final native boolean isAlive();

下面从底层源码中看一下notifyAll方法的调用:

    //位于\hotspot\src\share\vm\runtime\thread.cpp中
    void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
      // 其他(略)
      ensure_join(this);
    }
    static void ensure_join(JavaThread* thread) {
      // 其他(略)
      // 线程退出,将线程状态设置为TERMINATED
      java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
      // 从下一行代码可知,线程终止时,最终会回调到Java中的notifyAll方法
      lock.notify_all(thread);
    }   

(4)join案例1

public class TestJoin1 {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("run开始...");
                    /**
                     * 强调:join是同步方法,wait只能在同步方法或同步块中被锁对象调用
                     * 当前线程在自己的run方法中调用join,效果与直接调用wait方法类似:
                     * 1. 在自己的run方法中调用Thread.currentThread().join()相当于直接调用wait()或wait(0)
                     * 2. 在自己的run方法中调用Thread.currentThread().join(1000)相当于直接调用wait(1000)
                     */
                    Thread.currentThread().join(1000);
                    System.out.println("run结束...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

(5)join案例2

public class TestJoin2 {
    static class MyThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MyThread());
        Thread t2 = new Thread(new MyThread());
        t1.start();
        /**
         * 强调:这是在主线程中
         * 这里的t1.join()和t1.join(0)与下面代码功能类似:
         * synchronized (t1){
         *     // 到这里主线程会处于WAITING状态(无限期等待)
         *     t1.wait();
         * }
         */
        // t1.join();
        // t1.join(0);
        /**
         * 这里的t1.join(1000)与下面代码功能类似:
         * synchronized (t1){
         *     // 到这里主线程会处于TIMED_WAITING状态(限期等待)
         *     t1.wait(1000);
         * }
         */
        t1.join(1000);
        t2.start();
    }
}
2.2.5 yield
  // 静态native方法
  public static native void yield();

线程调用yeild,表示愿意让出CPU执行时间,但可能被调度器忽略。yeild是启发式的、尝试性的,它的使用应该与详细的概要分析和基准测试相结合,以确保它具有预期的效果。它在调试或测试时可能很有用,因为它可以帮助重现由于竞争条件造成的bug。

2.2.6 interrupt

(1)interrupt()

    // 设置中断标志位
    // 方法体略
    public void interrupt() {}

(2)interrupted()

    // 静态方法
    // 检测当前线程是否被中断(即当前线程的中断标志位是否被设置),
    // 被设置则返回true,否则返回false,调用interrupted()后清除中断标志位
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

(3)isInterrupted()

    // 非静态方法
    // 检测当前线程是否被中断,是则返回true,否则返回false
    // 与interrupted()的区别是调用isInterrupted() 后不会清除中断标志位 
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

(4) isInterrupted(boolean)

    // 供interrupted()和isInterrupted()调用的本地方法,
    //  ClearInterrupted为true表示清除中断标志位,为false表示不清除
    private native boolean isInterrupted(boolean ClearInterrupted);

当线程处于WAITING或TIMED_WAITING时,调用interrupt会清除中断标志位并抛出异常;当线程在进行I/O操作或选择操作时,调用interrupt会设置中断标志位并抛出异常;其余情况下调用interrupt,线程的中断标志位会被设置。

2.3 LockSupport中线程状态切换的相关方法
2.3.1 Unsafe中线程状态切换的相关方法

LockSupport中park、unpark等方法最终调用的是Unsafe的park和unpark,下面进行介绍:

    // native方法
    public native void park(boolean isAbsolute, long time);
    // native方法
    public native void unpark(Object thread);

当调用park()后,当前(调用者)线程会被阻塞,以下几种情况会唤醒当前线程:

  • 其他线程通过unpark唤醒当前线程(注意,在当前线程调用park之前或之后再调用unpark都会唤醒当前线程,且多次unpark只能唤醒一次park(前提是,多次unpark在这一次park之前))
  • 当前线程被中断(注意,只要中断标志位不被清除,每次park都会被唤醒)
  • isAbsolute为false,且指定的time不为0,会在一段时间后自动取消阻塞
  • isAbsolute为true,且超过time所指定的绝对时间,会自动取消阻塞
  • 也可能无理由地取消阻塞

注意取消阻塞后,外部需自行判断被park的线程是如何被唤醒的。与wait、notify等方法不同,park和unpark无须放在同步代码块或同步方法中,notify是随机唤醒某个阻塞着的线程(由具体的JVM实现决定),unpark可指定要被唤醒的线程。J.U.C包下各种并发机制就是通过CAS+自旋+park/unpark实现的。

2.3.2 LockSupport的park和unpark使用案例
    // 更正1.(3)中对park()的描述:当线程调用
    // park()后会进入WAITING状态,但不会释放锁
    LockSupport.park();
    // 阻塞10秒
    // 1000 * 1000 * 1000 * 10已经超出int类型的范围了,所以要加L后缀
    LockSupport.parkNanos(1000 * 1000 * 1000 * 10L);
    // 阻塞到系统时间达到System.currentTimeMillis()+3000(注意单位是毫秒)
    LockSupport.parkUntil(System.currentTimeMillis()+3000);
    // 唤醒指定线程
    LockSupport.unpark(thread);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容