线程状态转换浅析

进程是操作系统中程序运行的最小单位,一个进程内可以创建多个线程运行。因此线程在我们日常开发中接触的比较多,也是进行面试或排查问题经常被提及的。这篇文章我们就讨论一下线程状态的相关问题。

在linux中,可以通过 top -H 命令查看线程:
线程信息
也可以用 top -H -p <pid> 查看具体进程的线程数和相关信息。

操作系统中,进程是资源分配和调度的独立单位。因此要创建一个进程,就必须分配必要的系统资源,如内存、I/O设备等;要撤销进程,又要对这些资源进行回收;如果要在进程间进行切换,既要保留当前进程的CPU信息,又要分配新的CPU资源,这就需要消耗非常大的处理器资源。总而言之,进程不宜过多也不宜经常进行状态切换。那么要提升系统并发程度,又不想创建更多的进程,就需要在进程内部提升并发处理能力,由于线程轻量且可以共享分配给进程的资源,线程间状态切换的代价开销相对于进程也小很多,所以在系统中使用线程来提升并发处理能力变得可行。
在操作系统中定义线程共有5种状态:创建、就绪、运行、阻塞、死亡。从创建到结束运行,线程就在中间三种状态间进行切换,线程状态切换图如下:
操作系统线程状态图
因为日常开发中我都是使用java语言,下面稍微讲解一下在JVM中所定义的线程以及线程状态。
JVM中定义的线程状态与操作系统定义的线程状态有所不同,先上一个图:
JVM线程状态图

JVM虚拟机中的线程从创建(NEW)并且调用了start()方法后,直到TERMINATED状态为止。线程会在几个状态:RUNNABLE、BLOCKED、WAITING、TIMED_WAITING间来回切换,但要注意的是,线程处于RUNNABLE状态不一定在执行,也有可能在等待系统的其他资源(如处理器)。
java语言中线程的状态信息定义在Thread类中的State枚举,我们可以通过Thread实例的getState()方法获取到线程的状态信息,下面我们看看java中定义的线程状态以及对状态的描述(只是对代码中的英文进行简单翻译):

    /**
     * 一个线程在给定的时间点只能处于一种状态。这些状态是虚拟机状态,不反映任何操作系统线程状态
     */
    public enum State {
        /**
         * 尚未启动的线程处于此状态
         */
        NEW,

        /**
         * Java虚拟机中执行的线程处于此状态
         * 处于可运行状态的线程正在Java虚拟机中执行,
	     * 但它可能正在等待来自操作系统的其他资源,如处理器
         */
        RUNNABLE,

        /**
         * 等待获取监控器资源而被阻止的线程处于此状态。
	     * 处于阻塞状态的线程正在等待监视器锁进入同步块/方法,
	     * 或在调用后重新进入同步块/方法,如调用 Object.wait() 的方法
         */
        BLOCKED,

        /**
         * 无限期等待另一个线程执行特定操作的线程处于此状态
         * 由于调用以下方法之一,线程处于等待状态 : 
	     * Object.wait()、Thread.join()、LockSupport.park()
         *
         * 处于等待状态的线程正在等待另一个线程执行特定操作。
         *
         * 例如:一个线程在一个对象上执行了 Object.wait()
         * 正在等待另一个线程在同一个对象上调用 Object.notify() 或者 Object.notifyAll()。
         * 一个线程调用了 Thread.join() 正在等待指定的线程终止。
         */
        WAITING,

        /**
         * 在指定的时间内等待另一个线程执行操作的线程处于此状态
         * 具有指定等待时间的等待线程的线程状态。
	     * 调用以下方法之一线程将进入特定等待时间的等待状态 : 
         * Thread.sleep(long millis)
         * Object.wait(long timeout)
         * Thread.join(long millis)
         * LockSupport.parkNanos(long nanos)
         * LockSupport.parkUntil(long deadline)
         */
        TIMED_WAITING,

        /**
         * 已退出/终止的线程处于此状态
         * 线程已完成执行。
         */
        TERMINATED;
    }

下面我们通过一段java代码来获取线程在各个状态下的信息:

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 主线程
 * @author xingo
 *
 */
public class Test {
	public static Thread thread1;
	public static Thread thread2;

	public static void main(String[] args) throws InterruptedException {
		thread1 = new Thread(new Thread1(), "thread1");
		thread2 = new Thread(new Thread2(), "thread2");
		thread2.start();

		// 主线程休眠一段时间,保证thread2先于thread1的执行
		Thread.sleep(50);

		// thread1 - 初始状态
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + thread1.getName() + " 创建后状态 : " + thread1.getState());
		thread1.start();

		// 主线程休眠一段时间,保证thread2已经执行了同步方法:synMethod()
		Thread.sleep(50);
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + thread1.getName() + " 等待锁资源状态 : " + thread1.getState());

		try {
			// 主线程等待 thread1 终止
			thread1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + thread1.getName() + " 结束状态 : " + thread1.getState());
	}

	/**
	 * 这是一个同步方法,执行这个方法的线程会进入锁等待
	 */
	public static synchronized void synMethod() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

/**
 * 线程1
 * @author xingo
 *
 */
class Thread1 implements Runnable {

	@Override
	public void run() {
		// thread1 - 就绪状态
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + Test.thread1.getName() + " 执行start()方法状态 : " + Test.thread1.getState());
		// 执行同步方法
		Test.synMethod();
		try {
			// thread1 - 休眠一段时间,并保证休眠时间长于 thread2 线程,这样可以在 thread2 中输出 thread1 的线程状态
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try {
			// thread1 等待 thread2 终止
			Test.thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

/**
 * 线程2
 * @author xingo
 *
 */
class Thread2 implements Runnable {

	@Override
	public void run() {
		// 执行同步方法
		Test.synMethod();
		try {
			// thread2 - 休眠一段时间,由于 thread2 休眠时间比 thread1 休眠时间短,当它再次运行时 thread1 还处于超时等待状态
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + Test.thread1.getName() + " 超时等待状态 : " + Test.thread1.getState());
		try {
			// thread2 - 休眠一段时间,这样保证 thread1 再次运行后 thread2 线程还没有执行完
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()) + "|" + Test.thread1.getName() + " 等待状态 : " + Test.thread1.getState());
	}
}

上面的代码可以很好的验证线程在各个状态间如何进行转换,代码中也做了比较清楚的注释这里就不进行过多的解释,下面是代码运行后的输出信息:

13:44:50.466|thread1 创建后状态          : NEW
13:44:50.467|thread1 执行start()方法状态 : RUNNABLE
13:44:50.517|thread1 等待锁资源状态       : BLOCKED
13:44:51.587|thread1 超时等待状态         : TIMED_WAITING
13:44:54.587|thread1 等待状态            : WAITING
13:44:54.587|thread1 结束状态             : TERMINATED

总结:许多代码在单线程下运行都不会出现什么问题,但运行在多线程下就会变得很复杂也很难维护,归根结底就是因为多线程的运行和状态切换是由系统决定,不是我们能够干预的。上面的代码之所以能够获取到想要到结果,是因为线程还不够多系统不够繁忙;要应对更高并发量更高吞吐量的系统,多线程是现代系统开发中最常采用的方式,所以我们要更好的理解多线程的各个状态以及在各个状态间切换的方式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值