多线程的Thread 类及方法

news2025/1/23 14:56:11

请添加图片描述

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:海压竹枝低复举,风吹山角晦还明。

目 录

  • 🌲一. 线程的复杂性
  • 🌴二. Thread 类及常见方法
    • 📕2.1 Thread 的常见构造方法
    • 📗2.2 Thread 的几个常见属性
    • 📘2.3 启动一个线程-start()
    • 📙2.4 中断一个线程
    • 📓2.5 等待一个线程-join()
    • 📔2.6 获取当前线程引用
    • 📒2.7 休眠当前线程
  • 🌳三. 线程的状态
    • 🌻3.1 线程的所有状态
    • 🌹3.2 线程状态和状态转换

🌲一. 线程的复杂性

多线程是非常复杂的,以至于程序猿为了规避多线程代码而发明了很多其他的方法,来实现并发编程。

最复杂的地方实际上在于操作系统的调度执行

例如:主线程运行创建新线程,主线程中要完成 abcdef 个任务,而新线程要完成 123456 个任务

情况一:(时间线从上到下,按照时间先后顺序执行)

在这里插入图片描述

情况二:(时间线从上到下,按照时间先后顺序执行)

在这里插入图片描述

情况三:(时间线从上到下,按照时间先后顺序执行)

在这里插入图片描述
还有许许多多的情况,远远不止上面三种多线程最复杂的地方就在于操作系统 的 "随机" 调度,也可以理解为线程的创建是有先后顺序的,但是执行线程先后顺序是随机的!!!具体这个线程里的任务啥时候执行要看调度器!!!

 

🌴二. Thread 类及常见方法

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

每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

在这里插入图片描述

📕2.1 Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(“这是我的名字”);
Thread t4 = new Thread(new MyRunnable(), “这是我的名字”);

Thread(String name) --> 给线程起名字!线程在操作系统内核里,是没有名字的,只有一个身份标识,但是在 Java 中,为了能让程序猿调试的时候方便理解这个线程是谁,就在 JVM 中对应的 Thread 对象加了个名字(多个线程名字可以相同,不取名字也会有默认的名字)

public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }," 我的线程 ");
        t.start();

        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

例如上述代码中我们把线程命名为 " 我的线程 " 之后,我们可以根据上篇博客查看进程信息,在这个地方省略步骤。

在这里插入图片描述

如上我们就是进程命名成功了。

📗2.2 Thread 的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题后续详解

getId() --> 线程在 JVM 中的身份标识
 
线程的身份标识有好几个

  • 内核的 PCB 上有标识
  • 到了用户态线程库里也有标识。(pthread,操作系统提供的线程库)
  • 到了 JVM 中又有一个标识(JVM Thread 类底层也是调用操作系统的 pthread 库)
     
    标识各不相同,但是目的都是作为身份的区别

getName() --> 在 Thread 构造方法里传入的名字

getState() --> PCB 里有个状态,此处得到的状态是 JVM 里面设立的状态体系,比操作系统里的状态体系要丰富一些

getPriority() --> 获取到优先级

isDaemon() --> 守护线程(后台线程)。类似于手机 APP 前后台,线程分为前台线程和后台线程。一个线程创建出来默认是前台线程,前台线程会阻止进程结束,进程会保证所有的前台线程都执行完了才会退出;后台线程不会阻止进程结束,进程退出的时候不管后台线程是否执行完。 main线程就是一个前台进程,通过 t.setDaemon(true); 把线程设置为后台线程。

public class Demo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
           while(true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        },"我的线程");

        t.start();
        System.out.println(t.getId());
        System.out.println(t.getName());
        System.out.println(t.getState());
        System.out.println(t.getPriority());
        System.out.println(t.isDaemon());
        System.out.println(t.isAlive());

    }
}

在这里插入图片描述
上述操作都是获取到一瞬间的状态,不是持续状态

📘2.3 启动一个线程-start()

创建 Thread 实例,并没有真的在操作系统内核里创建出线程!调用 start 才是真正在系统里创建出新的线程,才真正开始执行任务!调用 start 方法, 才真的在操作系统的底层创建出一个线程。

创建 Thread 实例 --> 安排任务,各就各位。任务内容的体现在于 Thread 的 run 或者 Runnable 或者 lambda 。

线程的执行结束:只要让线程的入口方法(run,Runnable,lambda)执行完了,线程就随之结束了。(主线程的入口方法,就可以视为是 main 方法)

📙2.4 中断一个线程

  1. 手动创建标志位,来区分线程是否要结束

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

public class Demo9 {
    //用一个布尔变量表示线程是否要结束
    //这个变量是一个成员变量,而不是局部变量
    private static boolean isQuit = false;

    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(!isQuit){
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("新线程执行结束!");
        });
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("控制新线程退出!");
        isQuit = true;
    }
}

在这里插入图片描述

如上代码中控制线程结束,主要是这个线程有个循环,这个循环执行完毕就结束了。很多时候创建线程都是让线程完成一些比较复杂的任务,往往都是有一些循环(正是有这些循环,执行的时间才可能比较长一点),如果线程本身执行的很快,刷一下就完了,也就没有必要提前控制他结束的必要了。

Thread 中有内置的标志位,不需要咱们手动创建

  1. 使用 Thread 自带的标志位

在循环条件中改一下即可

Thread.currentThread().isInterrupted()

currentThread() --> 静态方法,获取到当前线程的实例(Thread 对象),这个方法总是会有一个线程调用他。线程 1 调用这个方法,就能返回线程 1 的 Thread 对象;线程 2 调用这个方法,就能返回线程 2 的 Thread 对象。
 
isInterrupted() --> 判定内置的标志位,为 true 表示线程要被中断,要结束。

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        Thread.sleep(5000);
        System.out.println("控制线程退出!");
        t.interrupt();
    }
}

在这里插入图片描述

调用 interrupt,产生了一个异常!异常出现了,线程还在运行!

注意:interrupt 方法的行为

  • 如果 t 线程没有处在阻塞状态,此时 interrupt 就会修改内置的标志位
  • 如果 t 线程正在处于阻塞状态,此时 interrupt 就让线程内部产生阻塞的方法,例如 sleep 抛出异常,interruptedException。

此处异常被 catch 捕获了,捕获之后,什么都没做,就只是打印个调用栈就完了,因此把异常的打印忽略即可。

正是因为这样的操作,程序猿就可以自行控制线程的退出行为了

  • 可以立即退出
break;
  • 可以等一会儿退出
try {
    Thread.sleep(1000);
} catch (InterruptedException ex) {
    ex.printStackTrace();
}
break;
  • 可以不退出
啥也不做,相当于忽略了异常

主线程发出 “退出” 命令时,新线程自己来决定如何处理这个退出行为

综上:Java 中终止新线程
线程阻塞:1. 立即停止 2. 待会停止 3.啥也不做
线程没有阻塞:通过标志位直接停止

📓2.5 等待一个线程-join()

线程之间的执行顺序是完全随机的,看系统的调度!我们不能确定两个线程的开始执行顺序,但是可以控制两个线程结束的顺序!

join 的顺序谁先调用谁后调用是无所谓的

t1.join();
t2.join();

如上先调用 t1.join 后调用 t2.join ,此时 t1 t2 开始运行了,main 先阻塞在 t1 这里

  1. 如果是 t1 先结束,t2 后结束。当 t1 结束的时候,main这里的 t1.join 就执行完毕了,继续执行 t2.join,就阻塞了,然后 t2 结束的时候,main 的 t2.join 也返回(结束阻塞),main 继续执行,完成计时操作。
  2. 如果是 t2 先结束,t1 后结束。当 t2 结束的时候,由于 t1 没结束,main 仍然在 t1.join 这里阻塞,当 t1 结束之后,t1.join 解除阻塞,main 继续执行到 t2.join,由于 t2 已经结束了,t2.join 就不再阻塞了,直接返回,main继续执行
  • 实现让 t2 等待 t1执行完,main 等待 t2 执行完
public class Demo11 {
    private static Thread t1 = null;
    private static Thread t2 = null;

    public static void main(String[] args) {
        t1 = new Thread(()->{
            System.out.println("t1 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
        t1.start();

        t2 = new Thread(()->{
            System.out.println("t2 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t2.start();

        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main end");
    }
}

  • 主线程先执行 t1 然后 join 等待 t1 执行完毕,t1 执行完之后,主线程再启动 t2 等待 t2 执行完毕
public class Demo12 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main begin");
        Thread t1 = new Thread(()->{
            System.out.println("t1 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
        t1.start();
        t1.join();

        Thread t2 = new Thread(()->{
            System.out.println("t2 begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t2.start();

        System.out.println("main end");
    }
}

join 的行为:

  • 如果被等待的线程还没执行完,就阻塞等待
  • 如果被等待的线程已经执行完了,直接就返回
方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度
  • 第一个是死等
  • 第二个和第三个设定了最大等待时间
  • 第三个的俩参数第一个是毫秒,第二个是纳秒,然后就是总的时间

未来实际开发程序的时候一般不会使用死等,会因为小的代码 bug 会让服务器卡死导致无法继续工作

📔2.6 获取当前线程引用

为了对线程进行操作(线程等待,线程中断,获取各种线程的属性),就需要获取到线程的引用

  • 如果是继承 Thread,然后重写 run 方法,可以直接在 run 方法中使用 this 即可获取到线程的实例,但是如果是 Runnable 或者 lambda,this 就不行了(this 就不是指向 Thread 实例)
  • 更通用的办法,Thread.currentThread()。哪个线程来调用这个方法,得到的结果就是哪个线程的实例。

📒2.7 休眠当前线程

  • 使用 sleep

在操作系统内核中有就绪队列(这里的PCB随时可以去CPU上执行),还有阻塞队列(这里的PCB暂时不参与调度,不去CPU上执行)。当某个代码中的线程调用 sleep 这个线程就从就绪队列跑到阻塞队列中,暂时不参与调度,等待 sleep 时间执行结束,然后就会调回就绪队列中(不是立马上 CPU 执行,还得看调度器的情况)。

 

🌳三. 线程的状态

🌻3.1 线程的所有状态

  • NEW: 安排了工作, 还未开始行动(创建了 Thread 对象,但是还没有调用 start 方法,系统内核里还没有线程)
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作(就绪状态:1. 正在 CPU 上运行;2. 还没在 CPU 上运行,但是一切准备就绪)
  • BLOCKED: 这几个都表示排队等着其他事情(等待锁)
  • WAITING: 这几个都表示排队等着其他事情(线程中调用了 wait)
  • TIMED_WAITING: 这几个都表示排队等着其他事情(线程中通过 sleep 进入的阻塞)
  • TERMINATED: 工作完成了(系统里面的线程已经执行完毕,销毁了,相当于线程的 run 执行完了,但是 Thread 对象还在)
public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{

                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

        });
        //start 之前获取,获取到的是线程还未创建的状态
        System.out.println(t.getState());

        t.start();
        Thread.sleep(500);//加上之后就是由 RUNNABLE 改成了 TIMED_WAITING
        System.out.println(t.getState());
        t.join();

        //join 之后获取,线程结束之后的状态
        System.out.println(t.getState());
    }
}

Thread.sleep(500);之前:(正在工作中)

在这里插入图片描述

Thread.sleep(500);之后:(正在 sleep 中)

在这里插入图片描述

🌹3.2 线程状态和状态转换

在这里插入图片描述
看起来复杂,简化起来就很简单:
在这里插入图片描述

主干道是 NEW => RUNNABLE => TERMINATED

在 RUNNABLE 会根据特定的代码进入支线任务,这些支线任务都是 "阻塞状态",这三种阻塞状态,进入的方式不一样,同时阻塞的时间也不同,被唤醒的方式也不同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/397054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dubbo的服务暴漏与服务发现源码详解

服务暴漏 如果配置需要刷新则根据配置优先级刷新服务配置 如果服务已经导出,则直接返回 是否异步导出(全局或者服务级别配置了异步,则需要异步导出服务) 服务暴漏入口DefaultModuleDeployer#exportServices private void exp…

Redis缓存穿透

缓存穿透: 缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上, 根本没有经过缓存这⼀层。举个例⼦:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量 请求,导致⼤量请求落到数据库。…

http笔记

文章目录1、什么是http?2、http报文格式3、请求报文1、认识URL2、认识http方法3、认识header4、响应报文5、https加密机制1、什么是http? http是应用层最广泛使用的协议之一;其中浏览器获取到网页就是基于http实现的;http就是浏览…

Caddy2学习笔记——Caddy2反向代理docker版本的DERP中继服务器

一、个人环境概述 本人拥有一个国内云服务商的云主机和一个备案好的域名,通过caddy2来作为web服务器。我的云主机系统是Ubuntu。 我的云主机是公网ip,地址为:43.126.100.78;我备案好的域名是:hotgirl.com。后面的文章…

【量化交易笔记】3.实现数据库保存数据

上一节,我们通过下载相关的 pandas 数据保存为 本地csv文件,这一节将上节的数据以数据库方式保存。 数据库保存 采集数据部分前一节已做说明,这里就直接用采用前面的内容。这里着重说明的事数据库连接。对与 python 相连接的数据库有很多&a…

玩转Python的交互(命令行)模式

我喜欢使用Python的交互界面(命令行模式)来运行和调试Python代码。为什么不用PyCharm、VSCode?因为先入为主,加上我的DOS命令行的情结,我第一次安装使用Python就是用这种黑白界面的,平时写代码惯用EmEditor…

MySQL慢查询

2 慢查询 2.1 慢查询介绍 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。具体指运行时间超过long_query_time值的SQL&…

软件测试之快速熟悉项目

快速熟悉项目 1、了解项目架构 C/S架构 C/S 代表的是客户端/服务器(client/server),这类软件的使用者需要在本地电脑安装客户端程序,例如:QQ。 优点:安全性高。 缺点:一旦软件有更新,用户需要手动下载&am…

Rust 开发系列PyO3:Rust与Python的联动编程(中)

第三节:对比C语言的Python原生扩展开发模式 C/c编写Python扩展的方法,与Rust大致是相同的,如果不论语言本身的语法带来的繁琐的话,就单纯以开发步骤和模式来看,原生语言写扩展的步骤更为标准和简单。 大致来说&#…

QT入门Item Views之QTreeView

目录 一、QTreeView界面相关 1、布局介绍 二、基本属性功能 1、设置单元格不能编辑 2、一次选中一个item 3、去掉鼠标移动到单元格上的虚线框 4、最后一列自适应 三、代码展示 1、创建模型,导入模型 2、 右键菜单栏 3、双…

深度学习模型训练工作汇报(3.8)

进行数据的初始整理的准备 主要是进行伪序列字典的设置,以及训练数据集的准备。 期间需要的一些问题包括在读取文件信息的时候,需要跳过文件的第一行或者前两行,如果使用循环判断的话,会多进行n次的运算,这是不划算的…

003+limou+HTML——(3)HTML列表

000、前言 列表是网页常见的一种数据排列方式,在HTMl中列表一共有三种:有序列表、无序列表、定义列表(另外“目录列表dir”和“菜单列表menu”已经在HTML5中被废除了,现在都是使用无序列表ul来替代) 001、有序列表&a…

C/C++指针与数组(一)

预备知识 1、数据的存储 2、基本内建类型 1)类型的大小 C offers a flexible standard with some guaranteed minimum sizes, which it takes from C: A short integer is at least 16 bits wide.An int integer is at least as big as short.A long integer is a…

Spring Cloud学习笔记:基础知识

这是本人学习的总结,主要学习资料如下 马士兵教育 目录1、Spring Cloud 简介2、Eureka3、建立Spring Cloud项目3.1、启动Server3.1.1、dependency3.1.2、配置文件3.1.3、Server端启动代码3.2、启动Client3.2.1、dependency3.2.2、配置文件3.3.3、Client端启动代码3…

Go之入门(特性、变量、常量、数据类型)

一、Go语言特性 语法简单并发性。Go语言引入了协程goroutine,实现了并发编程内存分配。Go语言为了解决高并发下内存的分配和管理,选择了tcmalloc进行内存分配(为了并发设计的高性能内存分配组件,使用cache为当前线程提供无锁分配…

电脑自动重启是什么原因?详细解说

案例:电脑自动重启是什么原因? “一台用了一年的电脑,最近使用,每天都会一两次莫名其妙自动重启,看了电脑错误日志,看不懂什么意思,一直找不到答案。有没有高手知道怎么解决这个问题的。” 当…

仿写简单IOC

目录 TestController类: UserService类: 核心代码SpringIOC: Autowired和Component注解 SpringIOCTest 类 ​编辑 总结: TestController类: Component public class TestController {Autowiredprivate UserService userService;public void test…

RocketMQ如何测试

RocketMQ如何测试MQ简介RocketMQRocketMQ测试点MQ简介 MQ:Message Queue,即消息队列,是一种应用程序之间的消息通信,简单理解就是A服务不断的往队列里发布信息,另一服务B从队列中读取消息并执行处理,消息发…

同步、异步ETL架构的比较

背景介绍: 数据的抽取,转换和加载 (ETL, Extract, Transform, Load) 是构建数据仓库过程中最复杂也是至 关重要的一个步骤,我们通常用两种办法来处理 ETL 流程: 一种是异步(Asynchronous) ETL 方式, 也称为文本文件(Flat file)方式。 另外…

华为云平台架构名词解释

名词解释 网络设备 ISW(外网接入交换机):出口交换机,常用于和外网建立静态/BGP路由互联 CSW (内网接入交换机):专线接入(用户内网骨干)交换机,用户自有网络…