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

  • 相关阅读:
    sqlmap注入教程
    Burp Suite使用详解一
    手工注入基本思路
    搜索型注入漏洞手工注入过程
    龙灵:特邀国内第一讲师“玄魂” 在线培训黑客神器Kali Linux
    linux grep命令详解
    linux ps命令详解
    linux DNS服务
    linux用户与组管理命令的基本操作
    canvas绘制坐标轴
  • 原文地址:https://www.cnblogs.com/-beyond/p/13143235.html
Copyright © 2011-2022 走看看