Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态

文章目录

  • 前言
  • 一、Thread类
    • 1, 构造方法
    • 2, 常用成员属性
    • 3, 常用成员方法
      • 3.1, start 启动线程
      • 3.2, interrupt 中断线程 (重点)
        • 3.2.1, 手动设置标记位
        • 3.2.2, 使用内置标记位
        • 3.3.3, interrupt 方法 的作用
      • 3.3 sleep 休眠线程
      • 3.4, jion 等待线程
      • 3.5 获取当前线程的引用
  • 二、线程的状态
    • 1, 观察线程的状态
    • 2, 状态转换
  • 总结


前言

各位读者好, 我是小陈, 这是我的个人主页
小陈还在持续努力学习编程, 努力通过博客输出所学知识
如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
希望我的专栏能够帮助到你:
JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等
Java数据结构: 顺序表, 链表, 堆, 二叉树, 二叉搜索树, 哈希表等
JavaEE初阶: 多线程, 网络编程, TCP/IP协议, HTTP协议, Tomcat, Servlet, Linux, JVM等(正在持续更新)

上篇[多线程基础1]主要介绍了 : 线程 的概念, 线程 和 进程 的区别, 以及如何创建线程
本篇继续介绍多线程相关的基础内容, 内容较多, 分为若干篇持续分享


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

一、Thread类

Thread类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread类 的对象与之关联

每一个线程, 都需要被描述(具体被描述什么, 下面再说), Thread类 的对象就是用来描述一个线程的

学习一个类, 要从它的构造方法学起


1, 构造方法

1️⃣ 无参构造方法 :

在上一篇介绍线程的创建方式时, 使用到了匿名内部类继承Thread, 重写 run 方法的方式, 这就是无参构造法的使用

        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("一个新的thread线程");
            }
        };

2️⃣ 一个参数的构造方法 : 参数是 String 类型的 name , 表示线程的名字

其实就是方法1 的创建线程方式, 构造方法参数可以传递一个"name", 唯一的作用只是程序员知道自己创建的线程是谁, 方便多线程中代码调试

		// 给这个线程命名为"喜羊羊"
        Thread thread = new Thread("喜羊羊") {
            @Override
            public void run() {
                System.out.println("一个新的thread线程");
            }
        };
        thread.start();
        // 输出线程的名字
        System.out.println("thread线程的名字是: " +  thread.getName());

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第1张图片

3️⃣ 一个参数的构造方法 : 参数是 Runnable 对象

在上一篇介绍线程的创建方式时, 使用到了lambda表达式的方式, 本质上就是匿名内部类实现了 Runnable 接口, 重写 run 方法, 把 Runnable 对象作为参数

        Thread thread = new Thread( () -> {
            while(true) {
                System.out.println("一个新的thread线程");
            }
        });

4️⃣ 两个参数的构造方法 : 一个是 Runnable 接口, 一个是 String 类型的 name

使用 lambda 表达式传参之后, 再传入一个字符串即可

        Thread thread = new Thread(() -> {
            System.out.println("一个新的thread线程");
        }, "美羊羊"); // 给这个线程命名为"美羊羊"
        thread.start();
        // 输出线程的名字
        System.out.println("thread线程的名字: " + thread.getName());

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第2张图片


2, 常用成员属性

1️⃣ ID . 获取 ID 使用 getId()
表示线程的唯一身份表示, 不会重复

2️⃣ 名称 . 获取名称使用 getName()
表示线程的名字, 对代码调试有帮助

3️⃣ 状态 . 获取状态使用 getState()
表示线程所处的情况, 下面会详细讨论

4️⃣ 优先级 . 获取优先级使用 getPriority()
优先级高线程的理论上会被优先调度

5️⃣ 是否为后台线程(默认为前台) . 获取是否为后台线程使用 isDaemon()
前台线程会阻止进程的结束, 后台线程不会, 啥意思?
java 进程中的所有前台线程都结束 java 进程才能结束, 后台线程不管是否结束, java 进程该结束就结束

可以通过 setDaemon() 把线程改为后台线程(true)

6️⃣ 是否存活 . 获取是否存活使用 isAlive()
run 方法结束了, 说明线程执行完了, 就不存在了(不存活了), 或者 run 方法执行之前, 线程也不存在(不存活)

7️⃣ 是否被中断 . 获取是否被中断使用 isInterrupted()
这个下面介绍 interrupt 方法时会介绍到

代码展示:

		Thread thread = new Thread(() -> {

        }, "美羊羊");

        System.out.println("-----调用 start 方法之前-----");
        System.out.println("thread线程 的状态 : " + thread.getState() + " (这是啥意思? 下面会介绍到)");
        System.out.println("thread线程 是否存活 : " + thread.isAlive() + " 此时 thread线程 不存在, 所以不存活");
        System.out.println(" ");

        thread.start();

        System.out.println("-----调用 start 方法时-----");
        System.out.println("thread线程 是否存活 : " + thread.isAlive() + " 此时 thread线程 正在执行, 所以不存活");
        System.out.println(" ");

        System.out.println("-----调用 start 方法之后-----");
        // 1, 获取thread线程的ID
        System.out.println("1, thread线程 的 id : " + thread.getId());

        // 2, 获取thread线程的名字
        System.out.println("2, thread线程 的名字 : " + thread.getName());

        // 3, 获取thread线程的状态
        System.out.println("3, thread线程 的状态 : " + thread.getState() + " (这是啥意思? 下面会介绍到)");

        // 4, 获取thread线程的优先级
        System.out.println("4, thread线程 的优先级 : " + thread.getPriority());

        // 5, thread线程是否为后台线程
        System.out.println("5, thread线程 是否为后台线程 : " + thread.isDaemon() + "默认都是false");

        // 6, thread线程是否存活
        System.out.println("6, thread线程 是否存活 : " + thread.isAlive() + " 此时 thread线程 已经执行完了, 所以不存活");

        // 7, thread线程是否被中断
        System.out.println("7, thread线程 是否被中断 : " + thread.isInterrupted());

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第3张图片


3, 常用成员方法

3.1, start 启动线程

这个方法已经使用过很多次了, 再做一些强调 :

重写了 run 方法只是用代码描述了线程要执行什么操作
上篇介绍了很多方式用来创建线程对象, 创建了线程对象不代表线程开始执行
程序员自己调用 run 方法虽然也会执行方法体中的代码, 但并没有启动新的线程
调用 start 方法才会启动一个线程, 这个被创建出来的线程才会真正独立执行


3.2, interrupt 中断线程 (重点)

3.2.1, 手动设置标记位

如果在 run 方法中执行循环打印, 循环条件为 true , 那么调用 start 方法启动线程后, 这个线程将无法结束


如果我们提前设置一个标记位 flag 为 false, 循环条件为 !flag , 并且每执行一次打印, 就休眠 1000 毫秒, 此时线程仍无法结束

使用 sleep 方法需要使用 try-catch 语句捕获一个 InterruptedException异常, 表示休眠期间被打断就会抛出异常
catch 语句中的 e.printStackTrace(); 就是在出现异常时用来打印调用栈信息

	// 成员属性 flag
    private static boolean flag = false;
    public static void main(String[] args)  {
        Thread thread = new Thread(() -> {
            while(!flag) {
                System.out.println("一个新的thread线程");
                try {
                	// 打印一次, 休眠 1 秒 
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

然后, 令主线程休眠 3000 毫秒后把 flag 设置为 true,

        thread.start();
        try {
            Thread.sleep(3000);
            flag = true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

此时 thread 线程中的循环不满足循环条件, 就会结束, 主线程 和 thread 线程的并发执行结果如下 : Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第4张图片


3.2.2, 使用内置标记位

上面是我们手动设置了标记位
而 Thread类 的常见属性中提到的 isInterrupted(), 就是内置的标记位, 默认为 false , 类似于刚刚代码里的 flag, 我们用 isInterrupted() 替换上面的 flag :

			// 省略其他代码
            while(!Thread.currentThread().isInterrupted()) {
            // 省略其他代码

Thread . currentThread() 是获取当前线程的示例, 也就是 thread
所以 : Thread . currentThread() . isInterrupted() 等同于 thread . isInterrupted()
只不过此时还没有完成对 thread 的初始化, 不能如后者那样写

接下来主线程中如何修改标记为呢? 就是使用 interrupt 方法 直接中断线程

		// 省略其他代码
        try {
            Thread.sleep(3000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

程序会如何执行呢? 需要先看看 interrupt 方法 到底有什么用


3.3.3, interrupt 方法 的作用

1️⃣ 会把标志位设为 true, 执行完 interrupt 之后 isInterrupted 的值就被设为 true 了
2️⃣ 如果线程处于阻塞状态, 就会取消阻塞状态, 上述代码中因为 sleep 方法, thread线程 会处于阻塞态(休眠), interrupt 就会让休眠结束, sleep 抛出异常,

所以, 按理说, 这个并发执行的程序最终会以一个异常结束
Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第5张图片

可以看到, catch 语句捕获了异常, 说明线程确实被打断了, 可是为什么线程没有结束, 还在循环打印呢 ? 原因是因为 sleep 方法


3.3 sleep 休眠线程

关于 sleep方 法的使用 以上代码已经用过很多次了, 这里再做个总结 :

参数为毫秒, 表示 sleep方法 执行后, 线程休眠多久
需要用 try-catch 捕获 InterruptedException 这个受查(编译时)异常, 如果休眠时被中断, 就会抛出异常

但是, sleep方法 还有一个重要操作 ! !
在休眠时如果被唤醒, sleep方法 会自动把标志位清空: 设置成 false

这就导致了在上述代码中, 为啥调用了 interrupt方法 后, 线程没有真正被中断, 就是因为标志位又被设置成了 false , 所以程序仍然会执行循环


sleep 方法为什么会这么做呢?

原因是, Thread类 并不希望线程执行了 interrupt方法 之后就立即被中断, 这样是很霸道很强硬的做法
它希望的效果是, interrupt方法 仅仅起到一个 “提示” 或者 “通知” 的作用, 然后由程序员编写代码, 用代码逻辑来控制线程是立即结束, 还是等一会再结束, 还是无视这个 “通知”

比如我女朋友让我别刷抖音了, 陪她出门逛街, 我完全可以立即关掉抖音
但如果我此时在努力敲代码, 有很重要的学习任务, 我可以和她商量, 能不能等我学习完再去
如果我无论什么状态下都立即中断手头的事儿, 显然是不合理的

站在我的角度来说, 只有我自己最了解, 最关心自己, 知道现在应该做什么

站在程序员的角度上, 只有程序员最了解, 最关心自己写的代码, 知道当前的代码是否应该结束, 所以交给程序员决定何时中断才是最优解

那么在上述代码中, 如何实现立即中断呢? 只需要在捕获异常之后加一个 break 即可

				// 省略其他代码
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
                // 省略其他代码

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第6张图片


3.4, jion 等待线程

有的时候, 多个线程不能满足并发的条件, 可能需要等某个线程执行完再并发执行

例如我要和女朋友去约会, 我应该去她家楼下接她, 如果我到她家楼下后, 她还在化妆, 我应该等她下楼之后再一起去约会

我们先再复习一下多线程的并发执行效果 :

        Thread thread = new Thread( () -> {
        	// 循环打印 5 次
            for (int i = 0; i < 5; i++) {
                System.out.println("thread线程");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        // 循环打印 5 次
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第7张图片

两条打印语句交替执行, 如果我在调用 start 方法 启动 thread线程 之后, 再写一个 thread.join(), 会发生什么呢?

		// 省略其他代码
        thread.start();
        thread.join();
        // 省略其他代码

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第8张图片
可以看到, 当 thread线程 执行完了所有的循环打印之后, 主线程 才开始循环打印, 说明此时, 主线程 是等待 thread线程 结束之后才执行的

所以 : 主线程调用 thread.join 就是 主线程 等待 thread线程

另外, join方法 还可以传入一个参数, 和 sleep方法 的参数一致, 单位是毫秒,

		// 省略其他代码
        thread.start();
        thread.join(2000);
        // 省略其他代码

这就表示主线程只等待 thread线程 2 秒钟, 时间一到, 不管 thread线程 是否执行完, 主线程都开始继续执行


3.5 获取当前线程的引用

上面已经使用过了, 这是一个静态方法, 会获取当前线程的引用, 然后可以继续进行其他操作

例如, 在 run 方法中执行完打印语句之后, 再打印一下当前线程的名字

        Thread thread = new Thread( () -> {
            for (int i = 0; i < 5; i++) {
                System.out.print("thread线程");
                System.out.println(" 我的名字叫 : " + Thread.currentThread().getName());
            }
        },"懒羊羊");
        thread.start();

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第9张图片


二、线程的状态

1, 观察线程的状态

线程有如下几种状态
1️⃣ NEW : 表示已经实例化了 Thread 对象, 但是还没有调用 start 方法启动线程

2️⃣ RUNNABLE : 表示就绪状态, 正在执行或者准备随时执行

3️⃣ TERMINATED : 线程执行完毕

4️⃣ TIMED_WAITING : 表示指定时间等待, 比如由于 sleep 方法, 线程休眠时的状态

5️⃣ BLOCKED : 表示等待锁出现的状态

6️⃣ WAITING : 表示由于 wait 方法出现的状态

我们先用代码观察一下前四个状态, 后两个状态以后再介绍

        Thread thread = new Thread( () -> {
            for (int i = 0; i < 5; i++) {

            }
        },"懒羊羊");

        System.out.println("调用 start 之前, thread线程的状态: " + thread.getState());
        thread.start();
        System.out.println("thread线程执行时的状态: " + thread.getState());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread线程结束时的状态: " + thread.getState());

Java【多线程基础2】 Thread类 及其常用方法 + 线程的状态_第10张图片

接下来看一下 thread 线程中调用 sleep方法, 线程休眠时的状态

        Thread thread = new Thread( () -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"懒羊羊");

        System.out.println("调用 start 之前, thread线程的状态: " + thread.getState());
        thread.start();
        Thread.sleep(1000);
        System.out.println("thread线程执行时的状态: " + thread.getState());

在这里插入图片描述


2, 状态转换

其实状态转换很简单, 很清晰, 说白了就是一条主线, 三条支线

一条主线
线程启动之前 : NEW 状态
线程执行时 : RUNNABLE 状态
线程结束之后 : TERMINATED 状态

1️⃣ 转换过程 : NEW --> RUNNABLE --> TERMINATED

三条支线
第一条支线 : 如果使用 sleep方法, 就进入TIMED_WAITING 状态
1️⃣ 转换过程 : NEW --> RUNNABLE --> TIMED_WAITING --> RUNNABLE --> TERMINATED

第二条支线 : 如果进行加锁操作, 就进入BLOCKED 状态
2️⃣ 转换过程 : NEW --> RUNNABLE --> BLOCKED --> RUNNABLE --> TERMINATED

第三条支线 : 如果使用 wait方法, 就进入WAITING 状态
3️⃣ 转换过程 : NEW --> RUNNABLE --> WAITING --> RUNNABLE --> TERMINATED

大致理解线程状态, 好处就是在后续进行多线程代码调试时, 能够根据当前线程的状态进行初步分析


总结

以上就是本篇的全部内容, 主要介绍了

Thread类中的 : 构造方法, 成员属性, 成员方法, 以及线程的 6 种状态
其中, interrupt方法 和 sleep方法 有个特殊点, 需要重点理解

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦~


上山总比下山辛苦
下篇文章见

你可能感兴趣的:(JavaEE初阶,java,开发语言,多线程,Thread类)