多线程之线程状态

操作系统的多任务

有两种实现多任务的方法,这取决于操作系统在中断程序时的行为——直接中断而不需要事先和被中断的程序协商,还是只有在被中断程序同意交出控制权之后才能执行中断。前者称为抢占式多任务,后者称为协作(非抢占式)多任务。抢占式多任务更加有效,但实现起来难度较大。而在协作多任务机制下,一个行为不当的程序可能会独占所有资源,导致其他所有程序无法正常工作(时说不当的程序不交出控制权,其他的线程任务无法被执行)。

多线程程序在更低的层次中引入多任务从而扩展了多任务的思想:单个程序看起来可以同时处理多个任务。通常将每个任务称为一个线程,它是控制线程的简称。可以一次运行多个线程的程序被称为多线程程序。

多线程和多进程的区别

本质区别在于每个进程有它自己的变量的完备集,线程则共享相同的数据。这听起来似乎有些危险,事实上也确实如此。尽管如此,对程序来说,共享的变量使线程之间的通讯比进程间的通讯更加有效而简单。而且,对于某些操作系统而言,线程比进程更“轻量级”,创建和销毁单个线程比发起进程的开销要小得多。

你可以通过构建一个Thread的子类来定义一个线程,然后构造一个子类的对象并调用他的start方法,然而并不建议你这样做。应该尽量从机制上减少需要并行运行的任务数。如果你有很多任务,为每个任务都创建一个独立的线程的代价就太大了,推荐使用线程池。

警告:不要调用Thread类或者Runnable对象的run方法。直接调用run方法只会在当前线程中执行任务,并不会启动新的线程(相当于在当前线程中的一个普通方法调用),正确的做法是调用Thread.start()方法,它会创建一个新的线程来执行run方法。

API:java.lang.Thread

  • static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  • Thread(Runnable target)  构造一个新的线程来调用指定target的run方法
  • void start() 启动这个线程,将引发调用run方法。这个方法将立即返回,并且新线程将并发运行
  • void run() 调用关联的Runnable的run方法

中断线程

线程将在它的run方法返回时终止。

可以使用interrupt方法来请求终止一个线程。该方法被调用时,会发送一个中断请求给当前线程。这个线程的中断状态将被设为true。如果这个线程当前被一个sleep调用阻塞,那么将抛出一个InterruptedException异常。使用interrupt方法并不能杀死线程,他只是将线程标记为中断状态,中断状态是可能再次回到可运行状态的,请参考下文中的线程状态。

当interrupt方法在一个线程上被调用时,该线程的中断状态(interrupted status)将会被置位。这是一个布尔类型的标志,存在于每一个线程之中。每个线程都应该不时地检查这个标志,以判断线程是否应该被中断。为了查明中断状态是否被置位,需要首先调用静态的Thread.currentThread方法来取得当前线程,然后调用它的isInterrupted方法:

while (!Thread.currentThread().isInterrupted()) {
	//do more work
}

如果一个线程被阻塞(是指阻塞状态,有很多种场景可以让线程阻塞,比方说Thread的sleep或者wait方法都可以造成线程的暂时阻塞)了,他就无法检查中断状态,而且还会抛出一个InterruptedException异常。

Thread的sleep等方法可以造成当前线程的阻塞,因此不要在sleep方法之后调用isInterrupted方法。否则会抛异常。


警告:当sleep方法抛出一个InterruptedException异常的时候,它同时也会清除中断状态。

interrupted和isInterrupted两个方法的区别:

这两个方法都能用来检测线程是否处于中断状态。interrupted方法是Thread类的静态方法,调用interrupted方法后会清除当前线程的阻塞状态。而isInterrupted方法是一个实例方法,调用isInterrupted方法后不会改变中断状态的值。

线程状态

线程可以有以下4个状态:

  • New——新生状态
  • Runnable——可运行状态
  • Blocked——被阻塞状态
  • Dead——死亡状态

新生状态:

当使用new操作符创建一个线程时,例如:用new Thread(Runnable),线程还没有开始运行(还没有调用start方法)。此时线程处于新生状态。

可运行状态:

当调用start方法后,该线程就进入了可运行状态了。start方法调用后,一个可运行线程可能实际上正在运行,也可以没有被执行,这取决于操作系统为该线程提供的运行时间(也就是之前说到的操作系统的多任务机制,是抢占式的还是协作式的,可运行状态的线程可能没有抢占到执行权而没有运行)。进入可运行的状态的线程被运行以后,并不是始终保持运行时的状态,可能会在运行中被中断,目的是使其他线程获得运行机会,也可能被其他的线程抢过去执行了。

目前市面上的桌面和服务器的操作系统都是使用抢占式调度,只有一些小型设备如手机系统,可能会采用协作式调度。因此运行时的线程可能随时会被其他的线程抢占过去执行,所以我们的称这个状态为Runnable而不是Running。

被阻塞状态:

当发生以下任何一种情况时,线程就会进入被阻塞状态:

  • 线程通过调用sleep方法进入睡眠状态
  • 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
  • 线程试图得到一个锁,而该锁正被其他线程持有
  • 线程在等待某个触发条件
  • 有人调用了线程的suspend方法。该方法已过时,我们不应该在自己的代码中使用。

通过以下途径中的一种,线程可以从被阻塞状态回到可运行状态:

  • 线程被置于睡眠状态,且已经经过指定的毫秒数——睡眠时间到了,该醒了。
  • 线程正在等待I/O操作的完成,且该操作已经完成——你完成了,该我了。
  • 线程正在等待另一个线程所持有的锁,且另一个线程已经释放了该锁的所有权(也有可能等待超时,我不等了)。
  • 线程正在等待某一个触发条件,且另一个线程发出了信号表明条件已经发生变化(也有可能等待超时,我不等了)。
  • 线程已经被挂起,且有人调用了它的resume方法。和suspend方法一样,该方法已过时,我们不应该在自己的代码中使用。

一个被阻塞的线程只能通过和他先前阻塞他的相同过程重新进入可运行状态。要特别注意,不能通过调用resume方法来解除被阻塞线程的阻塞状态。也就是说,上文介绍的进入阻塞状态与回到可运行状态必须是相互对应的。

死亡状态:

有两个原因会导致线程死亡,如下:

  • 因为run方法正常退出而自然死亡
  • 因为一个未捕获的异常终止了run方法而使线程猝死

特殊情况下,可以通过调用线程的stop方法来杀死一个线程。该方法将抛出一个ThreadDeath出错对象来杀死线程。但stop方法已经过时,我们不应该在自己的代码中调用它。

如何判断当前线程是否存活(要么是可运行的,要么是被阻塞的)

可以使用isAlive方法。如果线程是可运行或者被阻塞的,该方法返回true,如果线程处于新生状态或者是死亡状态,则返回false。

注意:我们无法确定一个活着的线程是可运行的,还是被阻塞的,也无法确定一个可运行的线程是否正在运行。另外,你也无法区分死亡线程和非可运行线程。


参考资料:

JAVA 核心技术 卷2:高级特性

猜你喜欢

转载自ktian.iteye.com/blog/1285521
今日推荐