【原创】Java并发编程系列10 | 线程状态

2021-03-15 04:29

阅读:499

标签:相关   tcl   无限   buffere   变量   超时   资源   而不是   out   

【原创】Java并发编程系列10 | 线程状态
收录于话题
#进阶架构师 | 并发编程专题
12个

点击上方“java进阶架构师”,选择右上角“置顶公众号”
20大进阶架构专题每日送达
技术图片
Java并发编程脑图
本文为何适原创并发编程系列第 10 篇,前面几篇没看过的,可以在文末找到前几篇的跳转链接。前面几篇理论知识介绍了一大堆,实际编程中线程应该怎么用呢?接下来就要开始介绍实际编程中如何操作线程,本文内容如下:

  • 如何创建并启动线程?
  • 创建并启动线程时需要注意些什么问题?
  • 线程都有哪些状态?这些状态之间如果转换的?
  • 应该如何查看线程的状态?

    1. 创建启动线程


两种方法

创建和启动线程两种方法:继承 Thread 类、实现 Runable 接口。
方法一:继承 Thread

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("创建线程");
    }
}

方法二:实现 Runable

public class Test {
    public static void main(String[] args)  {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("创建线程");
    }
}

创建线程实现

通过查看 Thread 类代码,不管是继承 Thread 类创建线程还是实现 Runable 创建线程,都是调用 Thread 类的 init 方法,来看下 init 方法:

private void init(ThreadGroup g, Runnable target, String name,
        long stackSize, AccessControlContext acc,
        boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    // 当前线程就是此新线程的父线程
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    g.checkAccess();

    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    // 新线程继承了parent线程的group、是否为Daemon、优先级priority、加载资源的contextClassLoader、可继承的ThreadLocal。
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = acc != null ? acc
            : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal
                .createInheritedMap(parent.inheritableThreadLocals);

    this.stackSize = stackSize;

    // parent线程为新线程分配一个唯一的ID
    tid = nextThreadID();
}

通过 init 方法可以看出:
新构造的线程对象是由其 parent 线程来进行空间分配的。
新线程继承了 parent 线程的 group、是否为 Daemon、优先级 priority、加载资源的 contextClassLoader、可继承的 ThreadLocal。
parent 线程会分配一个唯一的 ID 来标识这个 child 新线程。
启动线程

start()方法启动线程。
线程 start()方法的含义是:当前线程(即 parent 线程)同步告知 Java 虚拟机,只要线程规划器空闲,应立即启动调用 start()方法的线程。
注意:
启动线程调用 start()方法,而不是 run()。
启动一个线程前,最好为这个线程设置线程名称,因为这样在使用 jstack 分析程序或者进行问题排查时,就会给开发人员提供一些提示,自定义的线程最好能够起个名字。

2. 线程状态


状态

  1. 新建状态(NEW)

当程序使用 new 关键字创建了一个线程之后,线程就处于新建状态,此时的线程情况如下:
此时 JVM 为其分配内存,并初始化其成员变量的值;
此时线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体;

  1. 就绪状态(RUNNABLE)

当线程对象调用了 start()方法之后,线程处于就绪状态。此时的线程情况如下:
此时 JVM 会为其创建方法调用栈和程序计数器;
线程并没有开始运行,而是等待系统为其分配 CPU 时间片;

  1. 运行状态(RUNNING)

当线程获得了 CPU 时间片,CPU 调度处于就绪状态的线程并执行 run()方法的线程执行体,则该线程处于运行状态。
如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态;
如果在一个多处理器的机器上,将会有多个线程并行执行,处于运行状态;
当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象;
对于采用抢占式策略的系统而言,系统会给每个可执行的线程分配一个时间片来处理任务;当该时间片用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。此时线程就会又从运行状态变为就绪状态,重新等待系统分配资源。

  1. 阻塞状态(BLOCKED)

处于运行状态的线程在某些情况下,让出 CPU 并暂时停止自己的运行,进入阻塞状态。如:线程阻塞于 synchronized 锁。

  1. 等待状态(WAITING)

线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,唤醒线程之后进入就绪状态,如:
通过wait()方法进行等待的线程等待一个notify()或者notifyAll()方法;
通过join()方法进行等待的线程等待目标线程运行结束而唤醒;

阻塞在 java.concurrent 包中 Lock 接口的线程状态不是 BLOCK 状态,而是 WAITING 等待状态,因为 java.concurrent 包中 Lock 接口对于阻塞的实现均使用了 LockSupport 类中的相关方法。

  1. 超时等待状态(TIMED_WAITING)

线程进入了一个时限等待状态,如:sleep(3000),等待 3 秒后线程重新进入就绪状态。

  1. 死亡状态(DEAD)

线程会以如下 3 种方式结束,结束后就处于死亡状态:
① run()或 call()方法执行完成,线程正常结束;
② 线程抛出一个未捕获的 Exception 或 Error;
③ 直接调用该线程 stop()方法来结束该线程—该方法容易导致死锁,通常不推荐使用;
状态转换
技术图片

线程状态转换——《Java并发编程艺术》
查看线程状态

Java 服务器问题排查中,经常需要查看线程状态来定位问题。
查看如下示例代码的线程运行状态:

public class ThreadState {
    public static void main(String[] args) {
        new Thread(new RunThread(), "RunThread").start();// 一直处于RUNNABLE线程
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();// TIMED_WAITING线程
        new Thread(new Waiting(), "WaitingThread").start();// WAITING线程
        new Thread(new Blocked(), "BlockedThread-1").start();// 获取锁之后TIMED_WAITING
        new Thread(new Blocked(), "BlockedThread-2").start();// 获取不到锁,被阻塞BLOCKED
    }

    // 该线程一直运行
    static class RunThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                System.out.println(1);
            }
        }
    }

    // 该线程不断地进行睡眠 TIMED_WAITING
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 该线程在Waiting.class实例上等待 WAITING
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 该线程在Blocked.class实例上加锁后,不会释放该锁
    static class Blocked implements Runnable {
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Export 导出 jar 包 ThreadState.jar
Linux 环境或者 windows 的命令界面,运行 ThreadState.jar。
执行命令
java -cp ThreadState.jar test.ThreadState
到 java 安装目录(我的目录是:/usr/java/jdk1.8.0_161/bin)下,执行 ./jps ,显示如下:
5435 ThreadState
5435 就是 ThreadState.jar 程序的进程 id
执行命令 ./jstack 5435 ,查看线程状态


// 名为"BlockedThread-2" 的线程状态:TIMED_WAITING
"BlockedThread-2" #13 prio=5 os_prio=0 tid=0x00007fa8f0157800 nid=0x154f waiting on condition [0x00007fa8c962e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at test.ThreadState$Blocked.run(ThreadState.java:61)
        - locked  (a java.lang.Class for test.ThreadState$Blocked)
        at java.lang.Thread.run(Thread.java:748)

// 名为"BlockedThread-1" 的线程状态:BLOCKED
"BlockedThread-1" #12 prio=5 os_prio=0 tid=0x00007fa8f0156000 nid=0x154e waiting for monitor entry [0x00007fa8c972f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at test.ThreadState$Blocked.run(ThreadState.java:61)
        - waiting to lock  (a java.lang.Class for test.ThreadState$Blocked)
        at java.lang.Thread.run(Thread.java:748)

// 名为"WaitingThread" 的线程状态:WAITING
"WaitingThread" #11 prio=5 os_prio=0 tid=0x00007fa8f0154000 nid=0x154d in Object.wait() [0x00007fa8c9830000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on  (a java.lang.Class for test.ThreadState$Waiting)
        at java.lang.Object.wait(Object.java:502)
        at test.ThreadState$Waiting.run(ThreadState.java:46)
        - locked  (a java.lang.Class for test.ThreadState$Waiting)
        at java.lang.Thread.run(Thread.java:748)

// 名为"TimeWaitingThread" 的线程状态:TIMED_WAITING
"TimeWaitingThread" #10 prio=5 os_prio=0 tid=0x00007fa8f0152800 nid=0x154c waiting on condition [0x00007fa8c9931000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at test.ThreadState$TimeWaiting.run(ThreadState.java:31)
        at java.lang.Thread.run(Thread.java:748)

// 名为"RunThread" 的线程状态:RUNNABLE
"RunThread" #9 prio=5 os_prio=0 tid=0x00007fa8f0150800 nid=0x154b runnable [0x00007fa8c9a32000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileOutputStream.writeBytes(Native Method)
        at java.io.FileOutputStream.write(FileOutputStream.java:326)
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
        - locked  (a java.io.BufferedOutputStream)
        at java.io.PrintStream.write(PrintStream.java:482)
        - locked  (a java.io.PrintStream)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        - locked  (a java.io.OutputStreamWriter)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.write(PrintStream.java:527)
        - eliminated  (a java.io.PrintStream)
        at java.io.PrintStream.print(PrintStream.java:597)
        at java.io.PrintStream.println(PrintStream.java:736)
        - locked  (a java.io.PrintStream)
        at test.ThreadState$RunThread.run(ThreadState.java:20)
        at java.lang.Thread.run(Thread.java:748)

总结


Java 中创建线程两种方法:继承 Thread 和实现 Runable。线程的启动调用 start 方法,线程执行 run 方法。
新构造的线程对象是由其 parent 线程来进行空间分配的和线程 id 的,新线程继承 parent 线程的 group、是否为 Daemon、优先级 priority 等属性。
线程的状态:NEW、RUNNABLE、RUNNING、BLOCKED、WAITING、TIMED_WAITING、DEAD。
Java 服务器问题排查过程中,常用的 jstack 命令查看线程状态。
参考资料

《Java 并发编程之美》
《Java 并发编程实战》
《Java 并发编程的艺术》
技术和媒体实验室-Java 并发和多线程教程: http://tutorials.jenkov.com/java-concurrency/index.html
The Java? Virtual Machine Specification: https://docs.oracle.com/javase/specs/jvms/se8/html

并发系列文章汇总


【原创】01 | 开篇获奖感言
【原创】02 | 并发编程三大核心问题
【原创】03 | 重排序-可见性和有序性问题根源
【原创】04 | Java 内存模型详解
【原创】05 | 深入理解 volatile
【原创】06 | 你不知道的 final
【原创】07 | synchronized原理
【原创】08 | synchronized锁优化
【原创】09 | 基础干货

———— e n d ————
快年底了,师长为大家准备了三份面试宝典:
《java面试宝典5.0》
《350道Java面试题:整理自100+公司》
《资深java面试宝典-视频版》
分别适用于初中级,中高级,以及资深级工程师的面试复习。
内容包含java基础、javaweb、各个性能优化、JVM、锁、高并发、反射、Spring原理、微服务、Zookeeper、数据库、数据结构、限流熔断降级等等。
技术图片
获取方式:点“在看”,V信关注师长的小号:编程最前线并回复 面试 领取,更多精彩陆续奉上。

一、初中级《java面试宝典5.0》,对标8-13K

二、中高级《350道Java面试题:整理自100+公司》,对标12-20K

三、资深《java面试突击-视频版》,对标20K+

点在看好不好,喵~

【原创】Java并发编程系列10 | 线程状态

标签:相关   tcl   无限   buffere   变量   超时   资源   而不是   out   

原文地址:https://blog.51cto.com/15009303/2552823


评论


亲,登录后才可以留言!