Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态。以下为源码:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态
变迁如下图所示:
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
相关方法
- sleep()
强迫一个线程睡眠N毫秒,不会释放锁; - wait()
在synchronized代码块中使用,释放对象锁,释放CPU,进入等待池。 - start()
启动线程,真正实现了多线程运行.这时此线程是处于就绪状态, 并没有运行。无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。 - run()
为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行
run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。 - notify()
在synchronized代码块中使用,随机唤醒一个等待队列中的线程放入锁池,进行锁竞争,竞争到的线程进行执行。 - notifyAll()
在synchronized代码块中使用,唤醒等待池中的所有线程进入锁池,进行锁竞,竞争到的线程进行执行。
notify()或者notifyAll()调用时并不会真正释放对象锁, 必须等到synchronized方法或者语法块执行完才真正释放锁.
当某个线程从wait状态恢复出来的时候,要先获取锁,然后再退出同步块;所以notifyAll的实现是调用notify的线程在退出其同步块的时候唤醒起最后一个进入wait状态的线程;然后这个线程退出同步块的时候继续唤醒其倒数第二个进入wait状态的线程,依次类推.
- interrupt()
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程
本身并不会因此而改变状态(如阻塞,终止等)。
- 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会
因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。- 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出
InterruptedException,从而使线程提前结束 TIMED-WATING 状态。- 许多声明抛出InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异
常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。- 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程
thread的时候,可以调用thread.interrupt()方法,在线程的run方法内部可以根据
thread.isInterrupted()的值来优雅的终止线程。
- join()
等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,
回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。 - yield()
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。 - isAlive()
判断一个线程是否存活
线程结束方式
- 正常结束
run()或 call()方法执行完成,线程正常结束。 - 异常结束
线程抛出一个未捕获的 Exception 或 Error。 - 调用stop(线程不安全)
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。 - 使用退出标志退出线程
一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的
运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:
最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while循环
是否退出,代码示例:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() { while (!exit){
//do something
}
}
}
定义了一个退出标志 exit,当 exit 为 true 时,while 循环退出,exit 的默认值为 false.在定义 exit时,
使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只能由一个线
程来修改 exit 的值。
- Interrupt 方法结束线程
使用 interrupt()方法来中断线程有两种情况:
- 线程处于阻塞状态
如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。 - 线程未处于阻塞状态
使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行 break 跳出循环
}
}
}
}
等待池,锁池
当一个线程需要进行锁竞争,并且要竞争的锁被其他线程持有,那么将进入锁池;如果一个线程在执行的过程中执行了wait()方法,那么将会放入等待池,等待notify()或者notifyAll()唤醒放入锁池,进行锁竞争,竞争到的线程将会被执行。