Java面向对象系列[v1.0.0][线程的生命周期]

线程被创建并启动后,并不是直接进入了执行状态,它的生命周期要经过新建、就绪、运行、阻塞和死亡5个状态,尤其线程启动后,它不可能一直霸占着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、就绪之间切换

新建和就绪状态

使用New关键字创建了一个线程后,该线程就处于新建状态,此时它和其他Java对象一样仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值,此时线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体
当线程对象调用了start()方法后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态的线程并没有开始运行,只是表示该线程可以运行了,至于该线程何时开始运行,取决于JVM里线程调度器的调度

启动线程使用start()方法,而不是run()方法,调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理;如果直接调用线程对象的run()方法,run()方法会立即执行,而执行它的就是主线程,在它返回之前其他线程无法并发执行,系统把线程对象当成一个普通对象,而run()方法也是普通方法而不是线程执行体,程序就成为单线程程序了
如果调用了线程的run()方法之后,该线程就不再是新建状态,不能在此调用线程对象的start()方法,只能对处于新建状态的线程调用start()方法,否则将爆IllegalThreadStateException

public class InvokeRun extends Thread
{
	private int i;
	// 重写run方法,run方法的方法体就是线程执行体
	public void run()
	{
		for ( ; i < 100; i++)
		{
			// 直接调用run方法时,Thread的this.getName返回的是该对象名字,
			// 而不是当前线程的名字。
			// 使用Thread.currentThread().getName()总是获取当前线程名字
			System.out.println(Thread.currentThread().getName() + " " + i);   
		}
	}
	public static void main(String[] args)
	{
		for (var i = 0; i < 100; i++)
		{
			// 调用Thread的currentThread方法获取当前线程
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20)
			{
				// 直接调用线程对象的run方法,
				// 系统会把线程对象当成普通对象,run方法当成普通方法,
				// 所以下面两行代码并不会启动两条线程,而是依次执行两个run方法
				new InvokeRun().run();
				new InvokeRun().run();
			}
		}
	}
}

调用线程对象的start()方法后,该线程立即进入就绪状态,其实也就是等待执行状态,但并未真正进入运行状态
如果希望调用子线程的start()后子线程立即执行,可以让主线程等待一秒Thread.sleep(1),在一秒内CPU不会空闲,它会去执行另一个处于就绪状态的线程,从而立即执行子线程

运行和阻塞状态

处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态,当然在一个多处理器的机器里,将会有多个线程并行执行,而当线程数大于处理器数,依然会存在多个线程在同一个CPU上轮换的现象
一个线程开始运行后,不可能一直处于运行状态,除非他的线程执行体足够短,瞬间就执行结束,否则线程在运行过程中需要被中断,目的是使其他的线程获得执行的机会,这个线程调度的策略取决于底层平台,对于抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间段用完后系统会剥夺该线程锁占用的资源,让其他线程获得执行的机会,在选择下一个线程时,系统会考虑线程的优先级
大部分桌面和服务器操作系统都是抢占式调度策略,一些小型设备,例如手机可能采用协作式调度策略,该策略中只有当一个线程调用了它的sleep()或者yield()方法后才会放弃所占用的资源,换句话说必须由该线程主动放弃所占用的资源

当发生如下情况时,线程会进入阻塞状态:

  • 线程调用sleep()方法主动放弃所占用的处理器资源
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
  • 线索试图获得一个同步监视器,但该同步监视器正被其他线程所持有
  • 线程在等待某个通知
  • 线程调用了线程的suspend()方法将该线程挂起,这个方法容易导致死锁,应尽量避免使用
    被阻塞的线程会在何时的时候重新进入就绪状态,解除阻塞后必须重新等待线程调度器在此调度它
    当发生如下特定的情况时可以解除上面的阻塞
  • 调用sleep()方法的线程经过了指定时间
    线程调用的阻塞式IO方法已经返回
    线程成功的获得了试图取得的同步监视器
    正在等待某个通知的线程,获得了其它线程发出的通知
    处于挂起状态的线程被调用了resume()恢复方法
    在这里插入图片描述

线程死亡

线程会在如下三个方式结束,结束后的线程就处于死亡状态:

  • run()或call()方法执行完成,线程正常结束
  • 线程抛出一个未捕获的Exception或Error
  • 直接调用线程的stop()方法来结束该线程,该方法容易导致死锁,尽量少用
    当主线程结束时,其他线程不受任何影响,并不会随之结束,一旦子线程启动,那么它就拥有和主线程一样的地位。
    不要试图对一个已经死亡的线程调用start()方法重新启动,这种状态的线程无法再次作为线程执行
public class StartDead extends Thread
{
	private int i;
	// 重写run方法,run方法的方法体就是线程执行体
	public void run()
	{
		for ( ; i < 100; i++)
		{
			System.out.println(getName() + " " + i);
		}
	}
	public static void main(String[] args)
	{
		// 创建线程对象
		var sd = new StartDead();
		for (var i = 0; i < 300; i++)
		{
			// 调用Thread的currentThread方法获取当前线程
			System.out.println(Thread.currentThread().getName()	+ " " + i);
			if (i == 20)
			{
				// 启动线程
				sd.start();
				// 判断启动后线程的isAlive()值,输出true
				System.out.println(sd.isAlive());
			}
			// 只有当线程处于新建、死亡两种状态时isAlive()方法返回false。
			// 当i > 20,则该线程肯定已经启动过了,如果sd.isAlive()为假时,
			// 那只能是死亡状态了。
			if (i > 20 && !sd.isAlive())

			{
				// 试图再次启动该线程
				sd.start();
			}
		}
	}
}

  • isAlive()方法,当线程处于就绪、运行、阻塞三中状态时,该方法返回true,当线程处于新建、死亡状态时,该方法返回false
    不要对死亡状态的线程调用start()方法,程序只能对新建状态的线程调用一次start(),多次调用也是引发非法异常
发布了207 篇原创文章 · 获赞 124 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/105378109