欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

<基础-3> 线程状态及属性

程序员文章站 2022-03-06 22:40:58
...
1.线程状态
线程有如下6种枚举状态:
1)New新生态,2)Runnable 可运行态,3)Blocked被阻塞态 4)Waiting等待态 5)Timed waiting计时等待态 6)Terminated被终止态。
要确定一个线程的当前状态,可以调用Thread的实例方法getState()方法,返回State枚举对象

1.1 新生线程
当用new操作符创建一个新线程时,如new Thread(r), 该线程还没有开始运行。这意味着它的状态时new。此时程序还没开始运行线程中的代码。

1.2 可运行线程
一旦调用start()方法,线程处于runnable状态。一个“可运行”的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间。

1.3 被阻塞线程和等待线程和计时等待线程
当线程处在阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。

1) 当一个线程试图获取一个内部的对象锁(而不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,并且线程调度允许本线程持有它的时候,该线程将变成非阻塞状态。

2) 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。在调用Object.wait方法或Tread.join方法,或等待java.util.concurrent库中的Lock或Condition时,就会出现这种情况。实际上,被阻塞状态和等待状态是有很大不同的。简而言之:等待是等待cpu,阻塞是等待锁。

3) 有几个方法有一个超时参数,调用他们导致线程进入计时等待(timed waiting)状态。这一状态将一直保持到超时期满或接收到适当的通知,带有超时参数的方法有Thread.sleep和Object.wait,Thread.join, Lock.tryLock以及Condition.await的计时版。

Java的线程状态转换图:

<基础-3> 线程状态及属性
            
    
    博客分类: java 并行编程 线程状态中断线程interrupt线程优先级 

1.4 被终止的线程
线程因如下三个原因终止:
1) 因为run方法正常退出而自然死亡。
2) 因为一个没有捕获的异常终止了run方法而意外死亡。
3) 调用interrupt方法。

特别是,可以调用线程Thread的stop方法杀死一个线程,该方法抛出ThreadDeath错误对象,由此杀死线程。但是,stop方法已过时,不要在自己的代码中使用它(类似stop,还有suspend,resume方法也已经过时,后面会说明stop和suspend为何废弃)。

2. 中断线程
当线程的run方法执行完毕并经由执行return语句返回时,或出现了在方法中没有捕获的异常时,线程将终止。
在早起版本,Thread有一个stop方法,其他线程可以调用它终止线程,但现在已废弃了。

有一种可强制终止线程的方法,Thread的interrupt()方法,可以用来请求os终止线程(并不是立即终止)。当对一个线程调用interrupt()方法时,线程的中断状态将被置位。这是每一个线程都具有的boolean标志。可以调用Thread.currentThread.isInterrupted()判断当前线程是否中断。

但是,如果线程被阻塞(比如已经调用过了sleep()或wait()方法),就无法检测中断状态。这是产生InterruptedException的地方。阻塞库(比如Thread.sleep, Object.wait)方法都会自己检测线程何时中断,并且发现中断就提前返回,他们响应中断的操作为:清除中断标志+抛出InterruptedException
当在一个被阻塞的线程上调用interrupt()方法时,阻塞调用将会被InterruptedException中断(存在不能被中断的阻塞I/O调用,应该考虑选择可中断的调用)。也就是当同一个线程在sleep(或其他阻塞状态)状态时调用interrupt方法就会导致sleep终止,并抛出InterruptedException异常,同样如果一个线程在isInterrupedException返回true状态时调用sleep(或其他阻塞状态)时也会InterruptedException异常。

一般来说,阻塞函数,如:Thread.sleep、Thread.join、Object.wait、LockSupport.park等在检查到线程的中断状态时,会抛出InterruptedException,同时会清除线程的中断状态.

对中断的正确理解是:它不会立即中断正在执行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。

2.1 响应中断
通常有两种实用策略来响应中断:
1)传递异常(可能在执行某个特定于任务的清除操作之后),从而使你的方法也成为可中断的阻塞方法;
2)恢复中断状态,从而使调用栈中的上层代码能对异常进行处理。如下面示例2那样处理,Thread.currentThread().interrupt();



示例1:
public class InterruptedTest implements Runnable
{
    @Override
	public void run() 
	{
		while(true)
		{
			try
			{
				System.out.println("Thread sleep()");
				Thread.sleep(5000);
			}
			catch (InterruptedException e)
			{
				System.out.println("Thread interrupedException, and Thread.currentThread().isInterrupted() = " + Thread.currentThread().isInterrupted());
			}
		}
	}
    
    public static void main(String[] args)
    {
    	InterruptedTest run = new InterruptedTest();
    	Thread thread = new Thread(run);
    	
// 启动线程进入sleep状态。
    	thread.start();
    	
    // 立刻中断线程。
    	thread.interrupt();
    }
}
Main 方法输出:
Thread sleep()
Thread interrupedException, and Thread.currentThread().isInterrupted() = false
Thread sleep()
Thread sleep()
Thread sleep()
…


小示例2:
{
    @Override
	public void run() 
	{
		while(true)
		{
			try
			{
				System.out.println("Thread sleep()");
				Thread.sleep(2000);
			}
			catch (InterruptedException e)
			{
				System.out.println("Thread interrupedException, and Thread.currentThread().isInterrupted() = " + Thread.currentThread().isInterrupted());
				Thread.currentThread().interrupt();
				System.out.println("Thread interrupedException, and Thread.currentThread().isInterrupted() = " + Thread.currentThread().isInterrupted());
			}
		}
	}
    
    public static void main(String[] args)
    {
    	InterruptedTest run = new InterruptedTest();
    	Thread thread = new Thread(run);
    	
     // 启动线程
    	thread.start();
    	
    	// 中断线程触发中断异常,在异常里设置中断状态,导致每次调sleep时都异常。
    	thread.interrupt();
    }
}
Main方法输出:
Thread sleep()
Thread interrupedException, and Thread.currentThread().isInterrupted() = false
Thread interrupedException, and Thread.currentThread().isInterrupted() = true
Thread sleep()
Thread interrupedException, and Thread.currentThread().isInterrupted() = false
Thread interrupedException, and Thread.currentThread().isInterrupted() = true
Thread sleep()
Thread interrupedException, and Thread.currentThread().isInterrupted() = false
Thread interrupedException, and Thread.currentThread().isInterrupted() = true
Thread sleep()
…


通常捕获到中断异常后会设置中断状态或抛出异常处理不要忽略异常
对于InterruptedException的处理,可以有两种情况:
(1)外层代码可以处理这个异常,直接抛出这个异常即可
(2)如果不能抛出这个异常,比如在run()方法内,因为在得到这个异常的同时,线程的中断状态已经被清除了,需要保留线程的中断状态,则需要调用Thread.currentThread().interrupt()

没有任何语言方面的需求要求一个被中断的线程应该终止,中断一个线程不过是引起它的注意,被中断的线程可以决定如何响应中断。
如果在每次工作迭代之后都调用sleep方法(或其他可中断的方法),isInterrupted检测既没有必要也没有用处。因为,如果在中断状态被置位时调用sleep方法,它不会休眠而是清除这一状态并抛出InterruptedException。

有两个非常类似的方法,interrupted()和isInterrupted()。
1)Interrupted方法是一个静态方法,它既可以得到上一次线程的中断标志值,又可以同时清除线程的中断标志,一举两得,但同时也有坏处,就是这个函数有清除中断状态的副作用.
2)isInterrupted方法是一个实例方法,可用来检测线程是否被中断,调用这个方法不会改变中断状态。

3. 为什么弃用stop和suspend
Stop方法天生就不安全经验证明suspend方法会经常导致死锁

首先看stop方法,该方法终止所有未结束的方法,包括run方法。当线程被终止,立即释放被它锁住的所有对象的。这会导致对象处于不一致的状态。例如转账功能,当从一个账户已经扣款而没有存入另一个账户时线程终止。这样银行对象就被破坏了,因为锁已经被释放,这种破坏会被其他尚未停止的线程观察到。
当线程要终止另一个线程时,无法知道什么时候调用stop方法是安全的,什么时候导致对象被破坏,因此,该方法被弃用了。取而代之,在希望停止线程的时候应该中断线程(interrupt),被中断的线程会在安全的时候停止

与stop不同,suspend不会破坏对象。但是,如果用suspend挂起一个持有一个锁的线程,那么该锁在恢复之前是不可用的。如果调用suspend方法的线程视图获得同一个锁,那么程序死锁,被挂起的线程等着被回复,而将其挂起的线程等待获得锁。同样Object的sleep也不会释放锁。推荐使用Object的wait或Condition的await,这两者阻塞的时候都会释放占有的锁。

4. 线程属性
下面将讨论线程的各种属性,其中包括:线程优先级,守护线程,线程组以及处理未捕获异常的处理器。

4.1 线程优先级
在java程序中,每一个线程都有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。可以将线程优先级设置为MIN_PRIORITY(Thread类中定义为1)与MAX_PRIORITY(定义为10)之间的任何值。NORM_PRIORITY被定义为5.
每当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。但是,线程优先级高度依赖于系统。当虚拟机依赖于宿主平台的线程实现机制时,java线程的优先级被映射到宿主平台的优先级上,优先级个数也许更多,也许更少。
初级程序员常常过度使用线程优先级,不要将程序构建为功能的正确性依赖于优先级。

4.2 守护线程
可以通过调用t.setDaemon(true)将线程转换为守护线程(daemon thread)。守护线程的唯一用途是为其他线程提供服务。计时线程就是一个例子。
当只剩下守护线程时,虚拟机就退出了。

4.3 未捕获异常处理器
线程的run方法不能抛出任何被检测的异常(也就是run方法签名不能有throws关键字抛出任何非RuntimeException),但是,不被检测的异常会导致线程终止。在这种情况下,线程就死亡了。也就是:run方法只能抛出运行时异常RuntimeException

但是,不需要任何catch子句来处理可以被传播的异常(当一个异常被抛出以后,程序将控制权转移给try语句中第一个能够处理该异常的catch子句。这个从异常抛出到控制转移给合适的异常处理语句的过程就叫做异常传播)。相反,就在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。这个处理器必须属于一个实现Thread.UncaughtExceptionHandle接口的类。这个接口只有一个方法:
Void uncaughtException(Thread t, Throwable e)

从jdk5.0起,可以用setUncaughtExceptionHandler方法为任何线程安装一个处理器。也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认的处理器。如果不安装默认的处理器,默认的处理器就为空。但是,如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象。

ThreadGroup类实现Thread.UncaughtExceptionHandler接口。它的uncaughtException方法做如下操作:
1) 如果该线程组有父线程组,那么父线程组的uncaughtException方法被调用。
2) 否则,如果Thread.getDefaultExceptionHandler方法返回一个非空的处理器,则调用该处理器。
3) 否则,如果Throwable是ThreadDeath的一个实例,什么都不做(ThreadDeath对象由stop方法产生,而该方法已经过时)。
4) 否则,线程的名字以及Throwable的栈踪迹被输出到System.err上。


  • <基础-3> 线程状态及属性
            
    
    博客分类: java 并行编程 线程状态中断线程interrupt线程优先级 
  • 大小: 137.5 KB