04_线程状态转换方法

时间:2021-7-3 作者:qvyue

1、操作系统中的线程状态

操作系统中的线程状态有运行、就绪、等待三个关键状态

  • 就绪状态(ready):线程正在等待使用CPU,经调度程序调用之后可进入running状态
  • 执行状态(running):线程正在使用CPU
  • 等待状态(waiting): 线程经过等待事件的调用或者正在等待其他资源(如I/O)
04_线程状态转换方法
在这里插入图片描述

Q操作系统中的线程为什么没有挂起状态?

A由于线程不是资源的拥有单位,挂起状态对线程是没有意义的,如果一个进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去。可见由挂起操作引起的状态是进程级状态,不作为线程级状态。类似地,进程的终止会导致进程中所有线程的终止。

2、Java中的六个线程状态

Java中的线程状态有六个线程状态。

Thread.State源码

public enum State {
  
        NEW,

        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }

NEW 尚未启动的线程处于此状态。
RUNNABLE 在Java虚拟机中执行的线程处于这种状态。
BLOCKED 在等待监视器锁定的情况下被阻塞的线程处于此状态。
WAITING 无限期地等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING 正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。
TERMINATED 退出的线程处于此状态。
在给定的时间点,线程只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。

Q同一个线程调用两次start()方法是否可以?

A在调用一次start()之后,threadStatus的值会改变(threadStatus !=0),此时再次调用start()方法会抛出IllegalThreadStateException异常。

我们看一下start()方法的源码

    public synchronized void start() {

        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            //这个是native方法
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

猜想,new之后的threadStatus状态为0,但是调用native start0()方法后,状态会变为多少呢?我们一起debug一下。

package co.dianjiu.thread;

public class MyThreadStatus extends Thread{
    @Override
    public void run(){
        System.out.println("MyThreadStatus");
    }

    public static void main(String[] args) {
        MyThreadStatus myThreadStatus = new MyThreadStatus();
        System.out.println(myThreadStatus.getState());
        myThreadStatus.start();
        System.out.println(myThreadStatus.getState());
        myThreadStatus.start();
        System.out.println(myThreadStatus.getState());
    }
}

NEW
MyThreadStatus
RUNNABLE
Exception in thread “main” java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:789)
at co.dianjiu.thread.MyThreadStatus.main(MyThreadStatus.java:14)

看下getState()方法的源码

public State getState() {
        // get current thread state
        return jdk.internal.misc.VM.toThreadState(threadStatus);
    }

new 之后的 threadStatus断点进来的值为0

start 之后的 threadStatus断点进来的值为5

04_线程状态转换方法
在这里插入图片描述

04_线程状态转换方法
在这里插入图片描述

3、Java中的线程状态转换

04_线程状态转换方法
在这里插入图片描述

3.1、阻塞状态和就绪状态的转换

处于BLOCKED状态的线程是因为在等待锁的释放。假如这里有两个线程a和b,a线程提前获得了锁并且暂未释放锁,此时b就处于BLOCKED状态。我们先来看一个例子:

package co.dianjiu.thread;

public class MyThreadBlocked extends Thread{
    @Override
    public void run(){
        testMethod();
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadBlocked a = new MyThreadBlocked();
        a.setName("a");
        MyThreadBlocked b = new MyThreadBlocked();
        b.setName("b");
        a.start();
        // 需要注意这里main线程休眠了1000毫秒,而testMethod()里休眠了2000毫秒
        Thread.sleep(1000L);
        b.start();
        System.out.println(a.getName() + ":" + a.getState());
        System.out.println(b.getName() + ":" + b.getState());
    }

    // 同步方法争夺锁
    private static synchronized void testMethod() {
        try {
            Thread.sleep(2000L);
            System.out.println(Thread.currentThread().getName()+"====>"+Thread.currentThread().getState());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果

a:TIMED_WAITING
b:BLOCKED
a====>RUNNABLE
b====>RUNNABLE

3.2、等待状态和就绪状态的转换

从上面的转换图可知,有三种方法可从等待状态转换到就绪状态,让我们一起看一下具体的使用案例及每个方法的源码实现。

Object.wait()

调用wait()方法前线程必须持有对象的锁。

线程调用wait()方法时,会释放当前的锁,直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。

package co.dianjiu.thread;

public class MyThreadWaiting extends Thread{
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread a = new Thread(() -> {
            System.out.println("线程A等待获取lock锁");
            synchronized (lock) {
                try {
                    System.out.println("线程A获取了lock锁,将要运行lock.wait()方法进行等待");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        a.setName("a");
        Thread b = new Thread(() -> {
            System.out.println("线程B等待获取lock锁");
            System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
            synchronized (lock) {
                System.out.println("线程B等待获取lock锁,将要运行lock.notify()方法");
                lock.notify();
                //lock.notifyAll();
                System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
            }
        });
        b.setName("b");
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
        a.start();
        System.out.println(a.getName() + "===>" + a.getState());
        b.start();
        System.out.println(a.getName() + "===>" + a.getState());

    }
}

执行结果

a===>NEW
b===>NEW
线程A等待获取lock锁
线程A获取了lock锁,将要运行lock.wait()方法进行等待
a===>RUNNABLE
线程B等待获取lock锁
a===>WAITING
b===>RUNNABLE
线程B等待获取lock锁,将要运行lock.notify()方法
a===>RUNNABLE

看下wait方法的源码,其实wait()方法就等于wait(Long)方法值为0

public final void wait() throws InterruptedException {
        wait(0L);
    }

而唤醒方法调用的是Java本地接口(JNI)

其他线程调用notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。同样,调用notifyAll()方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的那个线程,具体要看系统的调度。

    @HotSpotIntrinsicCandidate
    public final native void notify();

    @HotSpotIntrinsicCandidate
    public final native void notifyAll();

Thread.join()

调用join()方法不会释放锁,会一直等待当前线程执行完毕(转换为TERMINATED状态)。

package co.dianjiu.thread;

public class MyThreadJoin extends Thread{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
    }
    public static void main(String[] args) throws InterruptedException {
        MyThreadBlocked a = new MyThreadBlocked();
        a.setName("a");
        MyThreadBlocked b = new MyThreadBlocked();
        b.setName("b");
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
        a.start();
        a.join();
        b.start();
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
    }
}

执行结果

a===>NEW
b===>NEW
a====>RUNNABLE
a===>TERMINATED
b===>TIMED_WAITING
b====>RUNNABLE

看下join方法的源码,其实join()方法就等于join(Long)方法值为0

public final void join() throws InterruptedException {
        join(0);
    }

LockSupport.park()

在线程调度的时候禁用当前线程,将其放置到WaitSet队列。除非有许可证。所谓的许可证就是前面说的0-1的标识。使用unpark则就会产生一个许可。
如果许可证可用,那么它将被消耗,并且调用将立即返回;否则,出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一:

  • 其他线程以当前线程为目标调用unpark方法。
  • 其他线程打断当前线程。
  • 调用了虚假的返回。
package co.dianjiu.thread;

import java.util.concurrent.locks.LockSupport;

public class MyThreadPark {
    public static void main(String[] args) throws InterruptedException {

        Thread a = new Thread(() -> {
            System.out.println("线程A获取了lock锁,将要运行LockSupport.park()方法进行等待");
            LockSupport.park(Thread.currentThread());
        });
        a.setName("a");
        Thread b = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
            System.out.println("线程B等待获取lock锁,将要运行LockSupport.unpark()方法");
            LockSupport.unpark(a);
            //a.interrupt();
            System.out.println(a.getName() + "===>" + Thread.currentThread().getState());
        });
        b.setName("b");
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
        a.start();
        System.out.println(a.getName() + "===>" + a.getState());
        b.start();
        System.out.println(a.getName() + "===>" + a.getState());

    }
}

a===>NEW
b===>NEW
线程A获取了lock锁,将要运行LockSupport.park()方法进行等待
a===>RUNNABLE
a===>WAITING
b===>RUNNABLE
线程B等待获取lock锁,将要运行LockSupport.unpark()方法
a===>RUNNABLE

3.3、超时等待和就绪状态的转换

Thread.sleep(long)

使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入RUNNABLE状态。

Object.wait(long)

wait(long)方法使线程进入TIMED_WAITING状态。这里的wait(long)方法与无参方法wait()相同的地方是,都可以通过其他线程调用notify()或notifyAll()方法来唤醒。

不同的地方是,有参方法wait(long)就算其他线程不来唤醒它,经过指定时间long之后它会自动唤醒,拥有去争夺锁的资格。

Thread.join(long)

join(long)使当前线程执行指定时间,并且使线程进入TIMED_WAITING状态。

我们再来改一改刚才的示例:

package co.dianjiu.thread;

public class MyThreadJoinTime extends Thread{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName() + "===>" + Thread.currentThread().getState());
    }
    public static void main(String[] args) throws InterruptedException {
        MyThreadBlocked a = new MyThreadBlocked();
        a.setName("a");
        MyThreadBlocked b = new MyThreadBlocked();
        b.setName("b");
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
        a.start();
        a.join(1000L);
        b.start();
        System.out.println(a.getName() + "===>" + a.getState());
        System.out.println(b.getName() + "===>" + b.getState());
    }
}

执行结果

a===>NEW
b===>NEW
a===>TIMED_WAITING
b===>BLOCKED
a====>RUNNABLE
b====>RUNNABLE

因为是指定了具体a线程执行的时间的,并且执行时间是小于a线程sleep的时间,所以a线程状态输出TIMED_WAITING。b线程状态仍然不固定(RUNNABLE或BLOCKED)。

3.4、线程中断状态

在某些情况下,我们在线程启动后发现并不需要它继续执行下去时,需要中断线程。目前在Java里还没有安全直接的方法来停止线程,但是Java提供了线程中断机制来处理需要中断线程的情况。

线程中断机制是一种协作机制。需要注意,通过中断操作并不能直接终止一个线程,而是通知需要被中断的线程自行处理。

简单介绍下Thread类里提供的关于线程中断的几个方法:

  • Thread.interrupt():中断线程。这里的中断线程并不会立即停止线程,而是设置线程的中断状态为true(默认是flase);
  • Thread.interrupted():测试当前线程是否被中断。线程的中断状态受这个方法的影响,意思是调用一次使线程中断状态设置为true,连续调用两次会使得这个线程的中断状态重新转为false;
  • Thread.isInterrupted():测试当前线程是否被中断。与上面方法不同的是调用这个方法并不会影响线程的中断状态。

在线程中断机制里,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程自己而定,可以在合适的实际处理中断请求,也可以完全不处理继续执行下去。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。