zoukankan      html  css  js  c++  java
  • Java 线程状态流转图

    一.线程状态流转图

      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

  • 相关阅读:
    〖Linux〗Kubuntu设置打开应用时就只在打开时的工作区显示
    〖Linux〗Kubuntu, the application 'Google Chrome' has requested to open the wallet 'kdewallet'解决方法
    unity, dll is not allowed to be included or could not be found
    android check box 自定义图片
    unity, ios skin crash
    unity, Collider2D.bounds的一个坑
    unity, ContentSizeFitter立即生效
    类里的通用成员函数应声明为static
    unity, Gizmos.DrawMesh一个坑
    直线切割凹多边形
  • 原文地址:https://www.cnblogs.com/-beyond/p/13143235.html
Copyright © 2011-2022 走看看