Java Thread系列线程状态

Posted binarylei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java Thread系列线程状态相关的知识,希望对你有一定的参考价值。

Java Thread系列(二)线程状态

一、线程的五种状态

技术分享图片

  1. 新建状态(New):新创建了一个线程对象,尚未启动。
  2. 就绪状态(Runnable):也叫可运行状态。线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。
  3. 运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    • 等待阻塞:运行的线程执行 wait() 方法,JVM 会把该线程放入等待池中。
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池中。
    • 其他阻塞:运行的线程执行 sleep() 或 join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
  5. 死亡状态(Dead):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。

二、线程调度

(1) 线程优先级

Java 线程有优先级,优先级高的线程会获得较多的运行机会。

Java 线程的优先级用整数表示,取值范围是 1~10,Thread 类有以下三个静态常量:

static int MAX_PRIORITY     // 线程可以具有的最高优先级,取值为 10。
static int MIN_PRIORITY     // 线程可以具有的最低优先级,取值为 1。
static int NORM_PRIORITY    // 分配给线程的默认优先级,取值为 5。

Thread 类的 setPriority() 和 getPriority() 方法分别用来设置和获取线程的优先级。

每个线程都有默认的优先级。主线程的默认优先级为 Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如 A 线程中创建了 B 线程,那么 B 将和 A 具有相同的优先级。
JVM 提供了 10 个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用 Thread 类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

(2) 线程睡眠

Thread.sleep(long millis) 方法,使线程转到阻塞状态。millis 参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep() 平台移植性好。

(3) 线程等待

Object 类中的 wait() 方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是 Object 类中的方法,行为等价于调用 wait(0) 一样。

(4) 线程让步

Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

(5) 线程 join

join() 方法,等待其他线程终止。在当前线程中调用另一个线程的 join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

(6) 线程唤醒

Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个 notifyAll(),唤醒在此对象监视器上等待的所有线程。

注意:Thread 中 suspend() 和 resume() 两个方法在 JDK1.5 中已经废除,不再介绍。因为有死锁倾向。

三、常用函数说明

(1) Thread.sleep(long millis)

sleep() 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

sleep() 不能改变对象的机锁,所以当在一个 Synchronized 块中调用 Sleep() 方法时,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

在 sleep() 休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

(2) join()

public class JoinTest extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("子线程:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            ;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JoinTest t = new JoinTest();
        t.start();
        t.join();   // (1)

        System.out.println("main 线程:" + Thread.currentThread().getName());
    }
}
  1. t.join() 会阻塞当前线程,即 main 线程,也就是说 main 线程会等待子线程执行完毕才会继续往下执行。

(3) Thread.yield()

Thread.yield() 方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

Thread.yield() 应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 Thread.yield() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

(4) setPriority()

更改线程的优先级。

MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10

(5) interrupt()

不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出异常,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!

(6) wait()

wait() 方法是 Object 类里的方法;当一个线程执行到 wait() 方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;

wait() 使用 notify 或者 notifyAlll 或者指定睡眠时间来唤醒当前等待池中的线程。

wiat() 必须放在 synchronized block 中,否则会在 program runtime 时扔出 “java.lang.IllegalMonitorStateException” 异常。

wait 和 sleep 区别

共同点:

  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait() 和 sleep() 都可以通过 interrupt() 方法 打断线程的暂停状态 ,从而使线程立刻抛出 InterruptedException。

如果线程 A 希望立即结束线程 B,则可以对线程 B 对应的 Thread 实例调用 interrupt 方法。如果此刻线程 B 正在 wait/sleep /join,则线程 B 会立刻抛出 InterruptedException,在 catch() {} 中直接 return 即可安全地结束线程。

需要注意的是,InterruptedException 是线程自己从内部抛出的,并不是 interrupt() 方法抛出的。对某一线程调用 interrupt() 时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出 InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join() 后,就会立刻抛出 InterruptedException 。

不同点:

  1. Thread 类的方法:sleep(),yield() 等。Object的方法:wait() 和 notify() 等
  2. 每个对象都有一个锁来控制同步访问。Synchronized 关键字可以和对象的锁交互,来实现线程的同步。
    sleep 方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用
  4. sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常

所以 Thread.sleep() 与 Object.wait() 方法的最大区别是:sleep()睡眠时,保持对象锁,仍然占有该锁;而wait() 睡眠时,释放对象锁。但是 wait() 和 sleep() 都可以通过 interrupt() 方法打断线程的暂停状态,从而使线程立刻抛出 InterruptedException(但不建议使用该方法)。

四、常见线程名词解释

主线程:JVM 调用程序 main() 所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过 Thread.currentThread() 来获取的进程。

后台线程(守护线程):指为其他线程提供服务的线程。JVM 的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束.

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过 isDaemon() 和 setDaemon() 方法来判断和设置一个线程是否为后台线程。

Thread 线程类的一些常用方法:

Thread.sleep(): 强迫一个线程睡眠N毫秒。 
isAlive(): 判断一个线程是否存活。 
join(): 等待线程终止。 
activeCount(): 程序中活跃的线程数。 
enumerate(): 枚举程序中的线程。 
currentThread(): 得到当前线程。 
isDaemon(): 一个线程是否为守护线程。 
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束) 
setName(): 为线程设置一个名称。 
wait(): 强迫一个线程等待。 
notify(): 通知一个线程继续运行。 
setPriority(): 设置一个线程的优先级。

每天用心记录一点点。内容也许不重要,但习惯很重要!




以上是关于Java Thread系列线程状态的主要内容,如果未能解决你的问题,请参考以下文章

java线程

怎样分析 JAVA 的 Thread Dumps

JVM故障分析系列之四:jstack生成的Thread Dump日志线程状态

Java中通过Runnable与Thread创建线程的区别

线程相关梳理

Java Thread系列线程创建