Java 线程生命周期的不同状态

在Java 5 以后,线程状态被明确定义在其公共内部枚举类型 java.lang.Thread.State 中。




java允许子线程获取主线程数据 java主线程等待子线程_Java


  • 新建(NEW),表示线程被创建处理还没有真正启动的状态,可以认为它是一个Java 内部状态。
  • 就绪(RUNNABLE),表示该线程已经在JVM 中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它CPU 片段,在就绪队列里面排队。
  • 阻塞(BLOCKED),表示线程在等待 Monitor lock。比如,线程试图通过synchronized 去获取某个锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。
  • 等待(WAITING),表示正在等待其他线程采取某些操作。一个场景的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似notify 等动作,通知消费线程可以继续工作了。Thread.join() 也会让线程进入等待状态。
  • 计时等待(TIMED_WAIT),其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如wait 或 join 等方法的指定超时版本,如下所示:

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

  • 终止(TERMINATED),不管是意外退出,还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡。

join() 的作用

让父线程等待子线程结束之后才能继续运行。

当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

真正发生的是调用 join() 的线程进入 TIMED_WAITING 状态,等待 join() 所属线程运行结束后再继续运行。

// 父线程

public class Parent extends Thread {

public void run() {

Child child = new Child();

child.start();

child.join();

// ...

}

}

// 子线程

public class Child extends Thread {

public void run() {

// ...

}

}

上面代码展示了两个类:Parent(父线程类),Child(子线程类)。

  • 在 Parent.run() 中,通过 Child child = new Child(); 新建 child 子线程(此时 child 处于 NEW 状态);
  • 然后再调用 child.start()(child 转换为 RUNNABLE 状态);
  • 再调用 child.join()。

在 Parent 调用 child.join() 后,child 子线程正常运行,Parent 父线程会等待 child 子线程结束后再继续运行。

  • 一个进程可以保护多个线程,作为任务的真正运行者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其他线程共享文件描述符、虚拟地址空间等。
  • 在Java 1.2 之后,Java 线程模型是一对一映射到操作系统内核线程的。

示例代码:

public class HelloWorldThread {

public static void main(String[] args) throws InterruptedException, ExecutionException {

Runnable task = ()->{System.out.println("Hello World!");};

Thread myThread = new Thread(task);

myThread.start();

myThread.join();

System.out.println("done");

}

}

查看Java应用中基本线程

写一个最简单的打印 HelloWorld 的程序,获取当前Java 所有线程:

public class HowManyThreads {

public static void main(String[] args) {

System.out.println("Hello world.");

ThreadGroup group = Thread.currentThread().getThreadGroup();

ThreadGroup topGroup = group;

//遍历线程组树,获取根线程组

while(group != null){

topGroup = group;

group = group.getParent();

}

int nowThreads = topGroup.activeCount();

Thread[] lstThreads = new Thread[nowThreads];

// 获取根线程组的所有线程

topGroup.enumerate(lstThreads);

for(int i=0; i

System.out.println("线程 number: " + i + " = " + lstThreads[i].getName());

}

}

}

输出结果(IDEA IDE 中运行结果):

Hello world.

线程 number: 0 = Reference Handler

线程 number: 1 = Finalizer

线程 number: 2 = Signal Dispatcher

线程 number: 3 = Attach Listener

线程 number: 4 = main

线程 number: 5 = Monitor Ctrl-Break

在命令行模式下,运行结果:


java允许子线程获取主线程数据 java主线程等待子线程_c++ 等待子线程结束_02


解释如下:

  • Reference Handler :它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
  • Finalizer: 用来执行所有用户Finalizer 方法的线程。
  • Attach Listener :线程是负责接收到外部的命令。

对该命令进行执行,并且把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。

  • Signal Dispatcher: 外部jvm命令的转发器;

前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispatcher线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispatcher线程也是在第一次接收外部jvm命令时,进行初始化工作。

  • Monitor Ctrl-Break : 如果使用的IDE是IDEA 直接运行会多一个Monitor Ctrl-break线程,这个是IDE的原因。
  • main : Java 应用主线程;


java允许子线程获取主线程数据 java主线程等待子线程_java允许子线程获取主线程数据_03