Java笔记··By/蜜汁炒酸奶

闲聊 Java 线程生命周期

在之前《闲话进程的生命周期》部分我们看了操作系统中理论上的生命周期,在这一节我们对应着看一下 Java 的线程周期。在 Java 中线程有如下几个状态:

public enum State {
  NEW,  
  RUNNABLE,
  BLOCKED,  
  WAITING,  
  TIMED_WAITING,  
  TERMINATED;  
}
1
2
3
4
5
6
7
8

Javax线程状态脑图

Java 中的这些状态是指线程在虚拟机中的状态,与操作系统无关。我们常说 Java 线程创建的是操作系统的线程,但操作系统众多,每个可能有自己的线程状态划分方式,JVM 通过自己定义状态并控制映射关系帮我们做了统一。

These states are virtual machine states which do not reflect any operating system thread states.

1 NEW

Thread state for a thread which has not yet started.

初始化或者说新建,表示线程尚未启动时,我们 new Thread() 创建实例但未执行start()方法时,线程会处于这个状态,可以通过线程实例的 getState() 方法查看。

getState() 调用 threadState(); 从而调用 jdk.internal.misc.VM.toThreadState(holder.threadStatus); 获取最终状态。

/**
*  如果线程的状态值和4做位与操作结果不为0,线程处于 RUNNABLE 状态。
*  如果线程的状态值和1024做位与操作结果不为0,线程处于 BLOCKED 状态。
*  如果线程的状态值和16做位与操作结果不为0,线程处于 WAITING 状态。
*  如果线程的状态值和32做位与操作结果不为0,线程处于 TIMED_WAITING 状态。
*  如果线程的状态值和2做位与操作结果不为0,线程处于 TERMINATED 状态。
*  如果线程的状态值和1做位与操作结果不为0,线程处于 NEW 状态。
*  否则线程处于 RUNNABLE 状态。
*/
public static Thread.State toThreadState(int threadStatus) { 
	// JVMTI_THREAD_STATE_RUNNABLE = 0x0004 ->   4
    if ((threadStatus & JVMTI_THREAD_STATE_RUNNABLE) != 0) {  
        return RUNNABLE; 
        //  JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400 -> 1024
    } else if ((threadStatus & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0) {  
        return BLOCKED;  
        // JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010 -> 16
    } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0) {  
        return WAITING;  
        // JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020 -> 32
    } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0) {  
        return TIMED_WAITING;  
        // JVMTI_THREAD_STATE_TERMINATED = 0x0002 -> 2
    } else if ((threadStatus & JVMTI_THREAD_STATE_TERMINATED) != 0) {  
        return TERMINATED;  
        // JVMTI_THREAD_STATE_ALIVE = 0x0001 -> 1
    } else if ((threadStatus & JVMTI_THREAD_STATE_ALIVE) == 0) {  
        return NEW;  
    } else {  
        return RUNNABLE;  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

新建时由于 Thread 内部未明确指定 threadStatus 的值,所以默认初始化为 0 ,最终获取状态时为 NEW

2 RUNNABLE

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.

可运行线程的线程状态。执行start()方法后会转为这个状态,处于该状态的线程会在 Java 虚拟机运行,此时线程也有可能处于等待计算机CPU等资源的情况。Java 线程的 RUNNABLE 实际包含了操作系统 readyrunning 两个状态。

2.1 start 能否多次执行

关于 start() 会引出多次执行的问题:

  1. 初始或运行期间是否能反复调用一个线程的 start() 方法
  2. 如果线程处于 TERMINATED 后再次调用该线程的 start() 方法

这两个情况都不能多次执行 start() 方法,一旦执行会报 java.lang.IllegalThreadStateException 异常,原因是 start() 方法在开始时做了 holder.threadStatus != 0 的状态校验,只有为0时才能执行。一旦执行start() 它的值便会改变,TERMINATED 属于线程执行结束的状态,此时状态为2,也不会为0。

public void start() {  
    synchronized (this) {  
        // zero status corresponds to state "NEW".  
        if (holder.threadStatus != 0)  
            throw new IllegalThreadStateException();  
        start0();  
    }  
}
1
2
3
4
5
6
7
8

2.2 yield 方法

yield() 方法是 Thread 类的一个静态方法,用于提示调度器前线程愿意放弃当前使用的处理器。调度程序可以忽略此提示。该方法很少被使用到,可能在调试或测试中有用。理论上该方法可以使操作系统线程从 running 转为 ready

JavaDoc中描述如下:

// 提示调度器前线程愿意放弃当前使用的处理器。调度程序可以忽略此提示。
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是一种启发式尝试,旨在改善线程之间的相对进度,避免一个线程过度使用 CPU。
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.

2.3 阻塞I/O

传统的IO操作是阻塞式的,因为 I/O 操作和CPU比起来太慢了,即使这么多年做了多种优化,依旧有着好多个数量级的差距,所以让CPU直接进行 I/O 操作会严重降低其效率。

解决方案就是当线程执行 I/O 操作时,释放CPU资源,进入等待队列,CPU从 ready 队列获取新的线程执行。这样I/O操作的线程不再执行,即进入阻塞状态(也称 waiting 状态,为了与《闲话进程的生命周期》中一致,我们这个系列统称 block阻塞状态)。线程在 CPU 是阻塞的,但在磁盘上是 running 状态,只是我们通常讨论操作系统中的线程状态时是围绕 CPU 说的

当磁盘执行完 I/O 操作时通过中断( interrupt )机制通知 CPU ,CPU 收到后进入中断处理阶段,手中运行的线程被中断,回到 ready 队列,而之前因为 I/O 操作被阻塞的线程因为 I/O 操作完成,也回到了 ready 队列,此时 CPU 有几率选择它来继续执行。有兴趣的可以去查阅一下直接存储器访问(DMA)方式,本篇不再详细展开。

在 Java 中线程运行阻塞I/O操作又会是什么状态呢?答案是 RUNNABLE。我们也可以起一个测试 Demo 验证一下,这里就不贴代码和VisualVM 监控验证效果了。

由此可知, JVM 中的 RUNNABLE 状态实际对应了操作系统线程中的 readyrunning 以及部分 block 状态。

3 BLOCKED

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 Object.wait.

Java 线程将 操作系统中的 BLOCKED 状态细分为了 BLOCKEDWAITINGTIMED_WAITING 三个状态。

BLOCKED 在这里是因等待 monitor 锁(也称监视器锁)而阻塞的线程状态。synchronized 底层涉及到 monitor 锁的加锁与释放锁,而 Object.wait 调用时需要在 synchronized 中进行, 所以处于这个 BLOCKED 阻塞状态的线程一定是在等待进入同步块/方法或者在调用Object.wait后重新进入同步块/方法。我们分两种情况细看。

3.1 进入(enter)同步块/方法时阻塞

这个比较好理解,一个线程进入同步块或者同步方法中就会加上 monitor 锁,在其释放锁之前,其余线程都会被阻塞在外面不能访问,这个锁的添加与释放无法手动控制,当锁可用时线程会自动从阻塞状态中恢复。

我们以起两个线程执行对一个计数器做+1操作为例。

/**  
 * 计数器类  
 */  
public class Counter {  
    int counter = 0;  
    /**  
     *  counter++ 操作  
     *  synchronized 保证线程安全  
     */  
    public synchronized void increase() {  
        counter++;  
        // 通过 sleep 模拟耗时较长逻辑  
        try {  
            Thread.sleep(10000);  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
    }  
}

// 测试类
public class CounterOfBlockedTest {  
    public static void main(String[] args) throws InterruptedException {  
        Counter c = new Counter();  
        // 线程 t1 创建并先执行  
        Thread t1 = new Thread(()->{  
            c.increase();  
        },"t1 线程");  
        t1.start();  
  
        // 线程 t2 创建并执行  
        Thread t2 = new Thread(()->{  
            c.increase();  
        },"t2 线程");  
        t2.start();  
        
        // 确保 t2 run已经得到执行  
        Thread.sleep(1000);  
        // 检测 t2 此时的状态  
        System.out.println("t2 state:"+t2.getState());  
    }  
  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

这是一个最简单的例子,我们可以结合 VisualVM 监控验证我们的逻辑,VisualVM 在 1.8 以后的版本不再主动提供,需要自行在 https://visualvm.github.io/download.html 获取。

t1 先执行,执行过程中基于 Thread.sleep(10000);进入等待状态,也就是图中的黄色部分。
Java线程图示

t2 执行时,由于是synchronized方法,所以会阻塞,一直等待 t1 的 monitor 锁释放,也就是上图浅蓝色部分。同时如下图截取的线程栈信息可看到t2一直在阻塞等待t1释放锁。
Java线程图示

3.2 wait 之后重进入(reenter)同步块时阻塞

一个线程在调用 Object.wait 方法之后,再次进入同步块/方法时,需要等待一个monitor 锁,该线程在这期间处于 blocked 状态。关于 `Object.wait 的运行过程大致如下:

  1. Object.wait/notify 的实现及其线程安全依赖 monitor 锁 ,必须在同步块/方法中运行,调用包含 Object.wait 的方法时,肯定要先获取锁并进入同步块,这是第一次 enter。
  2. Object.wait 一般与 while 结合,当满足 while 条件时,执行 Object.wait ,执行该语句之后会释放 monitor 锁,并将线程自己放入此锁对象的等待队列 wait set中。
  3. 其他线程 t2 执行的 notify 或者notifyAll 不会立即释放锁,需要等通知线程t2的同步块/方法执行完才会释放锁。
  4. 当收到 notify 或者notifyAll 后,wait线程需要重新获取到锁才能再次( reenter )进入同步块,并从上次 wait 的地方恢复执行,这是第二次 enter,所以叫 reenter。
  5. 锁并不会优先给它,而是需要和其他线程竞争锁,有可能是被其他线程先获得了锁,也可能是通知线程t2 执行 notify 类通知后没有立即结束,导致它没能获取到锁,从而进入了 BLOCKED 状态。

我们基于一段代码来演示这块

public class BookStorage {  
    // 总数  
    int num = 3;  
    /**  
     * 增加库存  
     * @param newNum  
     */  
    public  synchronized void storage(int newNum) {  
        // 增加库存  
        num += newNum;  
        // 通知  
        notify();  
        try {  
            Thread.sleep(10000); // 通知后却暂时不退出  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
  
    }  
    public synchronized void buy(int needNum) {  
        while (needNum > num) {  
            try {  
                wait();  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            }  
        }  
        // 实际待执行的 扣除库存的逻辑  
        num -= needNum;  
        // 为了监测状态变化  
        int i=0;  
        while (i<10000000) {  
            i++;  
            System.out.println("购书线程状态 end:"+num +", cout :"+i);  
        }  
    }  
}

public class BookStorageOfReenterBlockedTest {  
    public static void main(String[] args) throws InterruptedException {  
        Thread.sleep(10000); // 确保 VisualVM 监控到购书线程状态变更过程  
        BookStorage bs = new BookStorage();  
        Thread buy = new Thread(()->{  
            bs.buy(20);  
        },"购书");  
        buy.start();  
        System.out.println("购书线程状态 start:"+buy.getState());  
  
        Thread.sleep(10000); // 确保购书线程已经得到执行  
  
        Thread storage = new Thread(()->{  
            bs.storage(30);  
        },"补充库存");  
        storage.start();  
  
        Thread.sleep(100); // 确保购书线程已经被通知到  
        System.out.println("购书线程状态 end:"+buy.getState());  
  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

简单说一下过程:

  1. 有一个图书库存的对象,有补充库存 storage 和购买时减少库存 buy 两个方法,初始库存为 3。
  2. 购书线程 bs 先启动,并进入( enter )同步块,想要购买20本,但此时只有3本,发现不够,调用 wait ,锁释放,线程挂起( WAITING 状态,即图中黄色部分),进入 wait set 等待队列中。
  3. 之后 补充库存的线程 storage 启动,增加30本书的库存并通知( notify )购书线程 bs ,但继续在同步块中睡眠,导致锁没被立即释放。
  4. 购书线程收到通知后,从等待队列中移除,退出 WAITING 状态,但已经不持有锁,当尝试重新进入同步块以恢复到调用wait方法时的状态时,因为锁尚未被补充库存的线程释放,导致处于阻塞状态( BLOCKED 状态,即图中浅蓝色部分)。
  5. 最后补充库存的线程执行完成,购书线程得到锁,进入运行状态,即图中的绿色部分。

Java线程图示

这两种情况简单来说是一个意思,即因为没能获取得到 monitor 锁而无法进入同步块/方法时,线程处于 BLOCKED 阻塞状态

4 WAITING

A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

等待状态。等待状态的线程需要其他线程唤醒才能变成 RUNNABLE 状态。

调用下面这 3 个方法会使线程进入等待状态:

  • Object.wait() :Object.wait with no timeout ,使当前线程处于等待状态直到另一个线程唤醒它。
  • Thread.join() :Thread.join with no timeout ,会等待加入的线程执行完毕,对于平台线程底层调用的是 Object 的 wait 方法,对于虚拟线程底层涉及一个await() 方法。
  • LockSupport.park() :除非获得调用许可( permit ),否则禁用当前线程进行线程调度。该方式不需要依赖 monitor锁,所以没有 monitor锁释不释放的问题。

4.1 Condition.await 示例

我们知道想让线程进入等待除了 Object.wait() 还有类似的一个 Condition.await() ,为什么上面没有提及呢?因为 Condition.await() 底层涉及到 LockSupport.park() ,后者已经包含了 Condition.await() 这种情况 ,关于LockSupport 的具体情况我们后面再说。

我们继续用上面购书与增加库存的例子来说

public class BookStorage {  
    Lock lock = new ReentrantLock();  
    Condition condition = lock.newCondition();  
  
    // 总数  
    int num = 3;  
  
    /**  
     * 增加库存  
     * @param newNum  
     */    
    public void storageByLock(int newNum) {  
        lock.lock();    //锁定  
        // 增加库存  
        num += newNum;  
  
        try {  
            // 通知  
            condition.signal();  
            Thread.sleep(10000); // 通知后却暂时不退出  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  finally {  
            lock.unlock();  
        }  
    }  
  
    public void buyByLock(int needNum) {  
        while (needNum > num) {  
            lock.lock();    //锁定  
            try {  
                condition.await();  
            } catch (InterruptedException e) {  
                throw new RuntimeException(e);  
            } finally {  
                lock.unlock();  
            }  
        }  
        // 实际待执行的 扣除库存的逻辑  
        num -= needNum;  
        // 为了监测状态变化  
        int i=0;  
        while (i<10000000) {  
            i++;  
            System.out.println("购书线程状态 end:"+num +", cout :"+i);  
        }  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

执行状态如下:

Java线程图示
我们再看下橘红色区间的线程栈快照:
Java线程图示

可以很清晰的看到 Condition.await() 最终调用 LockSupport.park() 实现了线程等待。

4.2 join 相关

我们这里仅讨论平台线程中的 join,通过源码 wait(delay) 可知其底层是基于wait/notify机制的,相当于一种隐式调用。

假如有 a,b 两个线程,b 在 a 中调用了 b.join() ,相当于让 a 线程等待 b 线程执行完成。此时 a 会停止执行,b 执行完成后,系统会隐式通知 a 线程,使 a 线程停止等待恢复执行。在这里,线程 a 的等待条件就是 b 执行完成

public final void join(long millis) throws InterruptedException {  

// ... 
    synchronized (this) {  
        if (millis > 0) {  
            if (isAlive()) {  
                final long startTime = System.nanoTime();  
                long delay = millis;  
                do {  
                    wait(delay);  
                } while (isAlive() && (delay = millis -  
                         NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);  
            }  
        } else {  
            while (isAlive()) {  
                wait(0);  
            }  
        }  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

如果上面比较抽象,我们再换成主线程a ,和子线程 b 来解释一下。在主线程a 中调用了 b.join(),此时相当于主线程 a 调用了 b.wait(0) ,从而进入了子线程 b 的 monitor 的等待队列中,直到 b 执行完成通知 a , 主线程 a 才会恢复执行。主线程等待期间的栈信息大概是这样的:
Java线程图示

简而言之就是,wait 是一个对象的方法,当一个线程调用了这个对象的 wait 方法,这个线程就会进入该对象的 monitor 的等待队列中,直到被唤醒。

4.3 线程间的竞争与协作

多个线程为了获取同一个资源争抢互斥锁的过程是一种竞争关系,当一个线程获取后会阻塞其他线程的获取,其他线程进入 BLOCKED 状态,直到之前的线程释放锁,开启新一轮的竞争。

当线程获得锁进入后,发现资源不满足(比如上面的库存不满足自己购买数量),便有了等待的需求。但如果不释放锁,其他线程无法进入并调整资源(如增加足够多的库存),所以条件不满足时需要出来并释放锁。

如果只是退出并释放资源,但不等待,而是通过循环不断查询是否满足条件,有可能成功也可能失败,另外这种本身也是一种对 CPU 资源的浪费,这种情况类似所谓的忙等待 (busy waiting)

如果线程都不等待,而资源又不允许并发访问,调度器只能一个一个线程调度执行,此时有可能降低增加库存的线程被调度到的机率,虽然同步机制会尽量防止出现"饥饿(starvation)"现象,但执行效率依旧会被降低。

为了提高线程的执行效率,于是有了类似 Object.wait/notify 这种的等待/唤醒机制,在不满足条件时,调用Object.wait 退出执行并释放锁,进入等待队列,等到其他线程补充资源并调用Object.notify 通知和释放锁后,再进入之前执行Object.wait时,看是否满足条件,满足则执行,不满足继续等待,这也就是线程之间的协作关系。这实际上也是经典的“生产者与消费者”的问题。

5 TIMED_WAITING

Thread state for a waiting thread with a specified waiting time.

超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒。调用下面几个 个方法会使线程进入等待状态:

  • Thread.sleep :即 Thread.sleep(long millis) 这些带时间的方法,使当前线程睡眠指定时间。
  • Object.wait with timeout :即 Object.wait(long timeout) 这些带时间的方法,线程休眠指定时间,等待期间可以通过 notify()/notifyAll() 唤醒,如果 timeout 为 0,则会一直等待,等价于 Object.wait()
  • Thread.join with timeout:即 Thread.join(long millis) 这些带时间的方法,等待当前线程最多执行 millis 毫秒,如果 millis 为 0,则会一直执行;
  • LockSupport.parkNanos:即 LockSupport.parkNanos(long nanos) 这些带时间的方法, 除非获得调用许可,否则禁用当前线程进行线程调度指定时间。
  • LockSupport.parkUntil :即LockSupport.parkUntil(long deadline) 这些带时间的方法,与 parkNanos 类似,也是禁止线程进行调度指定时间,差距主要是涉及时间计算是否为绝对( absolute )的。

5.1 Object.wait 带超时时间的示例

我们继续以上面购书和增加库存为例,这里我们将 notify 移动到 sleep 之后,并在 wait 中加入时间,变为 wait(1000);

    public  synchronized void storage(int newNum) {  
        // 增加库存  
        num += newNum;  
        // 通知 -- 检测   Object.wait with no timeout//        notify();  
        try {  
            Thread.sleep(10000); // 通知后却暂时不退出  
        } catch (InterruptedException e) {  
            throw new RuntimeException(e);  
        }  
        // 通知 -- 检测  Object.wait with timeout        
        notify();  
    }
1
2
3
4
5
6
7
8
9
10
11
12

再看下运行效果:
Java线程图示

很明显购书线程提前被唤醒,但由于此时补充库存的线程还在执行并未释放锁,导致购书线程阻塞,一直到补充线程执行完成并释放锁才开始执行。

对应的我们再看下,如果 wait 不加超时时间的效果:
Java线程图示

也很明显购书线程一直在等待,直到补充库存的线程 notify() 且释放锁后,才被唤醒且获取得到锁进入了运行状态。

类似连接超时断开一样,这个超时唤醒也可以算是一种超时保障,避免执行 notify() 的线程意外中断无法通知等待线程,导致等待线程一直在等待队列。

5.2 虚假唤醒

虽然说 timeout 为 0(即Object.wait(0))等价 Object.wait(),表示会一直等待下去,但实际会存在“虚假唤醒”( spurious wakeup )的情况。

A thread can wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied.

线程可以在没有通知、中断或超时的情况下唤醒,这就是所谓的虚假唤醒。虽然这在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,并在条件不满足时继续等待来防范这种情况。

wait 应该总在循环中被调用,javadoc 中提供了样版代码:

 synchronized (obj) {      
	 while (<condition does not hold> and <timeout not exceeded>) { 
	   long timeoutMillis = ... ; // recompute timeout values    
       int nanos = ... ;          
       obj.wait(timeoutMillis, nanos);      
	 }      
       ... // Perform action appropriate to condition or timeout  
}
1
2
3
4
5
6
7
8

需要避免使用 if 来判断条件和调用 wait,否则一旦恢复,就会继续执行,不会再检测条件。由于存在“虚假唤醒”等情况,唤醒并不意味着一定符合条件

5.2 sleep 相关

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.

sleep 可以使当前执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和时钟的精度和准确度。不存在无限等待的情况,sleep(0) 会立即返回。

sleep 本身没有任何同步语义,本身与锁无关,也就不会涉及释不释放锁的问题,所以线程不会失去任何监视器的所有权。

6 TERMINATED

Thread state for a terminated thread. The thread has completed execution.

这个比较简单,终止状态,就是线程执行完成的状态。

7 总结

针对虚拟机线程状态可以得到如下一个转换图:
Java线程图示

我们将虚拟机给我们定义的线程状态与操作系统线程状态做个对比:
Java线程与操作系统线程状态

预览

相关资源

文中提到的源代码等相关资源
Loading comments...
0 条评论

暂无数据

example
预览