Java 线程状态流转图

2021-05-11 20:29

阅读:735

标签:循环   理论   状态   注意   创建线程   ext   异常   row   http   

一.线程状态流转图

  Java的线程可以有多种状态,在Thread.State类中定义了6个常量来表示线程的状态,分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED,下面是比较详细的一幅状态流转图:

  技术图片

 

 

 

 

 

二.示例代码

2.1 sleep

  先看下面一段代码,测试sleep的时候是否会释放已经获取到的资源:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestSleep {

    // 创建一个对象,作为synchronized的monitor(锁)
    private static Object obj = new Object();

    /**
     * 创建线程要完成的操作
     */
    public void work() {
        log.info("进入work,尝试获取monitor(obj)");
        // 获取监视器(monitor)
        synchronized (obj) {
            log.info("获取到monitor");
            try {
                // 进入的线程进行阻塞
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 测试sleep操作是否会释放已经获取的资源
     */
    @Test
    public void test() throws InterruptedException {
        Runnable runnable = this::work;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        t2.start();

        // 主线程阻塞等待t1和t2执行
        TimeUnit.SECONDS.sleep(1);
        log.info("t1的状态:{}, t2的状态:{}", t1.getState(), t2.getState());

        TimeUnit.SECONDS.sleep(100);
    }
}

  运行结果如下:

INFO  2020-06-16 18:08:14 [Thread-2] - 进入work,尝试获取monitor(obj)
INFO  2020-06-16 18:08:14 [Thread-1] - 进入work,尝试获取monitor(obj)
INFO  2020-06-16 18:08:14 [Thread-2] - 获取到monitor
INFO  2020-06-16 18:08:15 [main] - t1的状态:BLOCKED, t2的状态:TIMED_WAITING
INFO  2020-06-16 18:08:24 [Thread-1] - 获取到monitor

  从输出结果可以可以看到,t1和t2同时启动,t2先进入同步块,获取到monitor后,t2继续执行,进行10秒sleep,休眠过程中t2处于TIMED_WAITING状态。

  与此同时,由于t1未获取到monitor,只能阻塞等待t2释放monitor,此时t1处于BLOCKED状态;

  等待10秒过后,t1 sleep结束,t1退出同步代码块,t2这才获取到monitor。

  根据上面的例子总结一下,有以下几点:

  1.线程进行sleep操作时,不会释放已经获取的资源;

  2.线程进行sleep时,线程状态为TIMED_WAITING;

  3.因为没有获取到monitor而发生线程阻塞,此时线程处于BLOCKED状态。

 

2.2 wait与notify

  wait和后面的notify都是针对monitor来说的,可以进行线程间的通信。

  下面介绍

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;

/**
 * 描述:
 *
 * @author ganlixin
 * @create 2020-06-16
 */
@Slf4j
public class TestWaitAndNotify {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {

        Runnable doWaitRunnable = this::doWait;
        Runnable doNotifyRunnable = this::doNotify;

        // 两个线程执行相同的doWait,观察是否有线程阻塞
        Thread t1 = new Thread(doWaitRunnable);
        Thread t2 = new Thread(doWaitRunnable);
        Thread t3 = new Thread(doNotifyRunnable);

        // t1和t2会进行wait
        t1.start();
        t2.start();

        // 休眠3秒,等待t1和t2运行起来
        TimeUnit.SECONDS.sleep(3);
        log.info("t1的状态:{}, t2的状态:{}", t1.getState(), t2.getState());

        // 启动t3,进行notify
        t3.start();

        TimeUnit.SECONDS.sleep(10);
    }

    private void doWait() {
        log.info("doWait -> 进入到doWait");
        synchronized (monitor) {
            log.info("doWait -> 获取到monitor");
            try {
                // 阻塞
                monitor.wait();
                log.info("doWait -> wait状态解除");
            } catch (InterruptedException e) {
                log.info("doWait -> 捕获InterruptedException,e=", e);
            }
        }
    }

    public void doNotify() {
        log.info("doNotify -> 进入doNotify");
        synchronized (monitor) {
            log.info("doNotify -> 获取到monitor,开始notify");
            // notify会随机选择一个调用了monitor.wait而等待线程
            monitor.notify();

            // notifyAll,会通知所有调用了monitor.wait的线程
            // monitor.notifyAll();
            log.info("doNotify -> notify完成");
        }
    }
}

  运行上面的代码,运行结果如下:

INFO  2020-06-16 19:07:48 [Thread-2] - doWait -> 进入到doWait
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 进入到doWait
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 获取到monitor
INFO  2020-06-16 19:07:48 [Thread-1] - doWait -> 获取到monitor
INFO  2020-06-16 19:07:51 [main] - t1的状态:WAITING, t2的状态:WAITING
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> 进入doNotify
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> 获取到monitor,开始notify
INFO  2020-06-16 19:07:51 [Thread-3] - doNotify -> notify完成
INFO  2020-06-16 19:07:51 [Thread-2] - doWait -> wait状态解除

  从上面的运行结果可以看出,2个线程同时执行doWait,虽然有同步块synchronized(monitor),但是两个线程并没有发生阻塞,几乎同时获取到了monitor,获取到monitor后,t1和t2执行wait操作,此时两个线程的状态为WAITING状态;t3启动后,执行doNotify,同样能够获取都monitor,并进行notify操作,notify后,t2的wait状态接触。

  根据这个例子,总结一下:

  1.调用monitor的wait方法,会释放synchronized中的monitor,所以其他线程也可以获取到monitor,这点与sleep不同;

  2.调用monitor.wait方法后,线程处于WAITING状态;

  3.可以调用monitor.notify()或者monitor.notifyAll()来通知调用了monitor.wait()的线程,只不过notify()只会通知一个线程,并且这个随意选择一个线程通知,并不是按照wait顺序进行notify。

 

2.3suspend与resume

  suspend挂起线程,resume让一个挂起的线程继续执行

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestSuspendAndResume {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {
        Runnable runnable = this::doWork;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        t2.start();

        // 主线程休眠3秒,等待t1和t2执行
        TimeUnit.SECONDS.sleep(3);
        log.info("t1状态:{}, t2状态:{}", t1.getState(), t2.getState());

        log.info("开始resume t1");
        t1.resume();
        TimeUnit.SECONDS.sleep(1);
        log.info("t1状态:{}", t1.getState());

        log.info("开始resume t2");
        t2.resume();
        TimeUnit.SECONDS.sleep(1);
        log.info("t2状态:{}", t2.getState());

        TimeUnit.SECONDS.sleep(2);
        log.info("t1状态:{}, t2状态:{}", t1.getState(), t2.getState());
    }

    public void doWork() {
        log.info("doWork -> 进入doWork,尝试获取monitor");
        synchronized (monitor) {
            log.info("doWork -> 获取到monitor");

            // suspend挂起线程,接口已经是deprecated
            Thread.currentThread().suspend();
            log.info("doWork -> 挂起状态解除");
        }
    }
}

  运行结果,这里运行了10次,发现会有不同的结果:

  运行结果1如下

INFO  2020-06-16 19:52:47 [Thread-1] - doWork -> 进入doWork,尝试获取monitor
INFO  2020-06-16 19:52:47 [Thread-2] - doWork -> 进入doWork,尝试获取monitor
INFO  2020-06-16 19:52:47 [Thread-1] - doWork -> 获取到monitor
INFO  2020-06-16 19:52:50 [main] - t1状态:RUNNABLE, t2状态:BLOCKED
INFO  2020-06-16 19:52:50 [main] - 开始resume t1
INFO  2020-06-16 19:52:50 [Thread-1] - doWork -> 挂起状态解除
INFO  2020-06-16 19:52:50 [Thread-2] - doWork -> 获取到monitor
INFO  2020-06-16 19:52:51 [main] - t1状态:TERMINATED
INFO  2020-06-16 19:52:51 [main] - 开始resume t2
INFO  2020-06-16 19:52:51 [Thread-2] - doWork -> 挂起状态解除
INFO  2020-06-16 19:52:52 [main] - t2状态:TERMINATED
INFO  2020-06-16 19:52:54 [main] - t1状态:TERMINATED, t2状态:TERMINATED

  从这个结果中可以看出,t1和t2同时执行doWork,但是t1先获取到monitor,进入同步代码块,然后让t1调用suspend,将t1挂起,可以看到suspend让线程挂起的时候,并没有释放拥有的monitor,导致t2阻塞处于BLOCKED状态,而调用resume后的线程状态是RUNNABLE状态。

  注意结果1中可以看到,resume的顺序和suspend的顺序相同,即t1 suspend后,再resume t1;t2 suspend后,再resume t2;而后面马上给出的运行结果2,就出现resume顺序和suspend顺序不相同的情况:

  运行结果2如下:

INFO  2020-06-16 19:58:34 [Thread-2] - doWork -> 进入doWork,尝试获取monitor
INFO  2020-06-16 19:58:34 [Thread-1] - doWork -> 进入doWork,尝试获取monitor
INFO  2020-06-16 19:58:34 [Thread-2] - doWork -> 获取到monitor
INFO  2020-06-16 19:58:37 [main] - t1状态:BLOCKED, t2状态:RUNNABLE
INFO  2020-06-16 19:58:37 [main] - 开始resume t1
INFO  2020-06-16 19:58:38 [main] - t1状态:BLOCKED
INFO  2020-06-16 19:58:38 [main] - 开始resume t2
INFO  2020-06-16 19:58:38 [Thread-2] - doWork -> 挂起状态解除
INFO  2020-06-16 19:58:38 [Thread-1] - doWork -> 获取到monitor
INFO  2020-06-16 19:58:39 [main] - t2状态:TERMINATED
INFO  2020-06-16 19:58:41 [main] - t1状态:RUNNABLE, t2状态:TERMINATED

  从结果2中可以看出,

  1.t1、t2进入doWork,t2先获取到monitor进入同步代码块,t1由于未获取到monitor处于BLOCKED状态,t2由于调用了suspend方法(挂起)而处于RUNNABLE状态;

  2.先resume t1,但是t1的状态还是BLOCKED,然后resume t2,t2的挂起状态解除,t2释放monitor,t2完成任务后处于TERMINATED状态;

  3.t1获取到monitor,t1执行suspend,一直到程序结束,t1都处于RUNNABLE状态,而不是预期的TERMINATED状态,这是因为resume t2的操作发生在 suspend t2之前

  根据这段代码,以及这两个运行结果,可以得出下面的结论:

  1.调用suspend的线程不会释放已经拥有的资源;

  2.调用suspend后,且未调用resume时,线程处于RUNNABLE状态(就绪);

  3.resume可以发生在suspend之前,且不会抛出异常,只不过此时suspend线程将一直保持RUNNABLE状态。

  4.一定要防止出现resume发生在suspend之前,其实suspend和resume已经被废弃了,所以不使用这两个方法就ok了。

 

 

2.4 join

  join可以实现,等待一个线程结束后,再执行后面的操作,看下面的示例代码:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestJoin {

    @Test
    public void test() throws InterruptedException {

        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.info("开始执行");
                try {
                    log.info("进行sleep 3秒");
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("执行完毕");
            }
        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                log.info("开始执行");
                try {
                    log.info("等待t1完成");
                    t1.join();
                    log.info("已经等到t1完成工作,t2继续执行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("执行完毕");
            }
        };

        t1.start();
        t2.start();

        TimeUnit.SECONDS.sleep(1);
        log.info("t1状态:{}, t2状态:{}", t1.getState(), t2.getState());

        TimeUnit.SECONDS.sleep(10);
    }
}

  运行结果如下:

INFO  2020-06-16 20:27:54 [Thread-1] - 开始执行
INFO  2020-06-16 20:27:54 [Thread-2] - 开始执行
INFO  2020-06-16 20:27:54 [Thread-2] - 等待t1完成
INFO  2020-06-16 20:27:54 [Thread-1] - 进行sleep 3秒
INFO  2020-06-16 20:27:55 [main] - t1状态:TIMED_WAITING, t2状态:WAITING
INFO  2020-06-16 20:27:57 [Thread-1] - 执行完毕
INFO  2020-06-16 20:27:57 [Thread-2] - 已经等到t1完成工作,t2继续执行
INFO  2020-06-16 20:27:57 [Thread-2] - 执行完毕

  从上面的运行结果得出以下结论:

  1.调用t1.join()方法后,需要等待t1线程执行完毕后,t1.join()后面的语句才会执行;

  2.t2在等待t1执行完毕的过程中,t2处于WAINTING状态。

 

2.5 yield

  yield的功能:让当前线程“让”出cpu,注意,只是让出CPU,而不会释放已经获取到资源,看下面的示例即可:

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TestYield {

    private static Object monitor = new Object();

    @Test
    public void test() throws InterruptedException {

        Runnable runnable = this::doWork;

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(2);

        log.info("t1状态:{}, t2状态:{}", t1.getState(), t2.getState()); // RUNNABLE
        TimeUnit.SECONDS.sleep(10);
    }

    public void doWork() {
        log.info("doWork -> 进入doWork");
        synchronized (monitor) {
            log.info("doWork -> 获取到monitor,进行死循环yield");
            while (true) {
                Thread.yield();
            }
        }
    }
}

  运行代码,结果如下:

INFO  2020-06-16 20:50:48 [Thread-2] - doWork -> 进入doWork
INFO  2020-06-16 20:50:48 [Thread-1] - doWork -> 进入doWork
INFO  2020-06-16 20:50:48 [Thread-2] - doWork -> 获取到monitor,进行死循环yield
INFO  2020-06-16 20:50:50 [main] - t1状态:BLOCKED, t2状态:RUNNABLE

  可以看到,t1和t2同时进入doWork,但是t1先获取到monitor进入同步代码块,然后t1一直死循环进行yield,而yield的时候,并没有释放资源(monitor)。

  所以,yield理论上会“礼让”CPU,但是礼让CPU的同时,并不会释放已经获取的资源。 

 

2.6 park和unpark

  park用于让当前线程等待(WAITING),unpark将处于等待的线程恢复正常。

package cn.ganlixin.thread;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * 描述:
 *
 * @author ganlixin
 * @create 2020-06-16
 */
@Slf4j
public class TestParkAndUnpark {

    @Test
    public void test() throws InterruptedException {

        Thread t1 = new Thread() {
            @Override
            public void run() {
                log.info("开始park");
                LockSupport.park();
                log.info("WAITING状态解除");
            }
        };
        t1.start();

        // 让t1先执行几秒
        TimeUnit.SECONDS.sleep(2);
        log.info("t1的状态:{}", t1.getState());

        log.info("开始执行unpark t1");
        LockSupport.unpark(t1);
        TimeUnit.SECONDS.sleep(20);
    }
}

  输出结果如下:

INFO  2020-06-16 23:38:26 [Thread-1] - 开始park
INFO  2020-06-16 23:38:28 [main] - t1的状态:WAITING
INFO  2020-06-16 23:38:28 [main] - 开始执行unpark t1
INFO  2020-06-16 23:38:28 [Thread-1] - WAITING状态解除

  从上面的运行结果可以看出,线程调用park后阻塞,线程进入WAITING状态,当调用unpark的时候,并没有抛出InterruptedException

 

Java 线程状态流转图

标签:循环   理论   状态   注意   创建线程   ext   异常   row   http   

原文地址:https://www.cnblogs.com/-beyond/p/13143235.html


评论


亲,登录后才可以留言!