线程的四种操作

news2024/9/16 14:30:13

  所属专栏:Java学习     

在这里插入图片描述

 

1. 线程的开启

start和run的区别:

run:描述了线程要执行的任务,也可以称为线程的入口

start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api会在内核中创建线程)

start执行的速度是比较快的,一旦 start 执行完毕,新线程就会开始执行,调用start的线程,main线程也会继续执行

在Java中,一个Thread对象只能对应到一个系统中的线程,在start中就会根据线程的状态来判断,如果Thread对象没有start,此时的状态就是一个new状态,可以顺利调用start,如果已经调用过start,就会进入到其他状态,只要不是new状态,都会抛出异常

2. 线程的终止

当线程B正在运行时,如果发生了特殊情况需要终止掉线程,有两种实现方式:

  1. 通过共享的标记来进行沟通
  2. 调用interrupt()方法来通知

先来看使用自定义的isQuit作为标志位的例子:

public class ThreadDemo4 {
    private static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (!isQuit){
                    System.out.println("thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("thread线程终止了");
            }
        };
        thread.start();
        Thread.sleep(1000);
        System.out.println("main线程尝试终止thread线程...");
        isQuit = true;
    }
}

这时就有一个问题需要注意:

如果isQuit不用static修饰,改为main方法里的局部变量可行不可行?ans:肯定是不可行的

这就涉及到了lambda表达式变量捕获的问题了

变量捕获是lambda表达式/匿名内部类中的一个语法规则:isQuit和lambda表达式定义在一个作用域中,此时lambda内部是可以访问到外部(和lambda同一个作用域)中的变量的,Java中的变量捕获是有一个特殊要求的,要求捕获的变量必须是final或事实final(虽然不是final修饰,但是后面没有更改)

就如上面的代码中,isQuit后面是被修改了的,所以就违反了语法规则

刚开始的形式为什么可以:写在外面就是外部类的成员变量,内部类本来就是可以访问外部类的

再来看interrupted的例子:

Thread类里面有一个boolean类型的成员变量interrupted,初始状态为false,一旦外面有线程调用interrupt方法,就会设置标志位为true

public class ThreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            Thread currentThread = Thread.currentThread();
            while (!currentThread.isInterrupted()){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        //主线程中控制thread被终止,设置标志位
        thread.interrupt();
    }
}

看起来是和自定义的标志位一样的,但是运行之后就会发现出现了异常:

由于在循环中判断和打印的操作太快了,整个循环的时间都是花在sleep方法里的,当main中调用interrupt时,大概率线程thread是在sleep中的,此时Interrupt就不仅能设置标志位,还可以唤醒thread线程,就会抛出InterruptedException异常,catch中捕获到异常也是做了抛出处理,就交给了JVM,程序就异常终止,那么把处理异常的方式更改一下试试:

这时就会发现while循环中的代码一直在执行

这里的原因是当sleep等阻塞函数被唤醒之后,就会清空刚刚设置的标志位,这时interrupted就一直是初始状态,也就导致了死循环,如果需要结束循环,可以把刚刚的异常处理直接改为break。

Java中终止线程的方式:A线程希望B线程能够终止,B线程收到这样的请求之后就可以自行决定是否是立即终止,稍后终止还是直接无视

  1. 如果B线程想要无视A,catch中就什么也不做,B线程就继续执行(sleep清除标志位)
  2. 如果B线程想要立即结束,就在catch中直接break或return
  3. 如果B线程想要稍后再终止,就可以在catch中添加其他的逻辑(释放资源,清理数据,提交结果...),这些完成之后再break/return.

3. 线程的等待

在之前提到过,操作系统针对多个线程的执行是一个“随机执行,抢占式调度”的过程,哪条线程先执行和先结束是不确定的,不过可以通过使用线程等待来决定哪条线程先结束,也就是让最后结束的线程等待先结束的线程,此时后结束的线程就进入了阻塞状态

例如在a线程中调用b.join(),就是让a线程等待b线程先结束,然后a再继续执行

public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread1结束了");
        });
        thread1.start();
        System.out.println("main线程开始等待");
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("main线程结束等待");
    }
}

上面的代码是thread1线程先执行,然后main线程开始等待,进入阻塞状态,如果说修改为先让thread1线程结束,main线程再开始等待会如何呢?

此时join并没有发生阻塞,join方法就是确保被等待的线程能够先结束,如果已经结束了,就没有等待的必要了

此外,任何线程都可以等待别的线程,而且可以等待多个线程,或者是多个线程之间互相等待

public class ThreadDemo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for(int i = 0;i < 3;i++){
                System.out.println("线程t1执行...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t1结束");
        });
        Thread t2 = new Thread(()->{
            for(int i = 0;i < 3;i++){
                System.out.println("线程t2执行...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程t2结束");
        });
        t1.start();
        t2.start();
        System.out.println("main线程开始等待...");
        t1.join();
        t2.join();
        System.out.println("main线程结束等待");
    }
}

这就是main线程同时等待两个线程的例子,关于两个join的顺序其实没有区别,最终都是等待了4s

在main等待t1和t2同时,t2也可以等待t1:

这样就把原来t1和t2并发执行修改为了t1先执行

main线程也可以被其他线程等待,不过写法不同的是,需要先获取main线程的引用

public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread thread = new Thread(()->{
            System.out.println("thread等待main线程");
            try {
                mainThread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("结束等待");
        });
        thread.start();
        Thread.sleep(1000);
        System.out.println("main线程结束");
    }
}

在上面使用的join方法中,由于是没有传入参数的,就表示被等待的线程只要没有执行完,就会一直等待,这种方式肯定是不好的,如果被等待的线程出现问题了,就会使这个等待操作一直进行,所以就有了传参的版本,但上面列举的第三个高精度的一般也用不到

4. 线程的休眠

之前一直用的Thread.sleep()这个操作就是让调用的线程阻塞等待一定时间的,线程执行sleep之后,就会使这个线程不参与CPU的调度,把CPU的资源让出来,给其他线程使用,在开发时,如果发现某个线程的CPU占用率过高,就可以通过sleep来改善,虽然说线程的优先级也可以影响,但比较有限

在这里插入图片描述

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

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

相关文章

C++17: 用折叠表达式实现一个IsAllTrue函数

前言 让我们实现一个 IsAllTrue 函数&#xff0c;支持变长参数&#xff0c;可传入多个表达式&#xff0c;必须全部计算为true&#xff0c;该函数才返回true。 本文记录了逐步实现与优化该函数的思维链&#xff0c;用到了以下现代C新特性知识&#xff0c;适合对C进阶知识有一定…

2025最新:如何打造公司财务管理系统?Java SpringBoot实现,一步到位管理企业财务!

✍✍计算机毕业编程指导师** ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java…

STM32F407VET6开发板RT-Thread的移植适配

前言 最近购买了一块 STM32F407VET6开发板【魔女】&#xff0c;http://www.stm32er.com/ 通过原理图了解到&#xff0c;开发板板载 CMSIS-DAP 调试下载口&#xff0c;例程部分大部分以裸机程序为主 目标&#xff1a;打算移植适配到 RT-Thread 适配 RT-Thread RT-Thread 支持…

基于准静态自适应环型缓存器(QSARC)的taskBus万兆吞吐实现

文章目录 概要整体架构流程技术名词解释技术细节1. 数据结构2. 自适应计算队列大小3. 生产者拼接缓存4. 高效地通知消费者 小结1. 性能表现情况2. 主要改进3. 源码和发行版 概要 准静态自适应环形缓存器&#xff08;Quasi-Static Adaptive Ring Cache&#xff09;是taskBus用于…

【Python报错已解决】 No Python at ‘C:\Users...\Python Python39\python.exe’

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;检查Python安装路径2.2 …

苍穹外卖随记(一)

黑马苍穹外卖逻辑和细节的问题和解决 1.后端查询到员工的日期信息&#xff0c;将信息进行json化传给前端时发生&#xff1a;前端收到的是不标准的日期json串。 解决&#xff1a;1.注解进行json格式化&#xff08;JsonFormat&#xff09;2. 在spring MVC中&#xff0c;通过消息…

如何验证VMWare WorkStation的安装?

如何验证VMWare WorkStation的安装&#xff1f; 右击"网络"&#xff0c;点击 打开"网络和Internet设置"&#xff0c;点击更改适配器选项&#xff0c;如果出现VMNet1和VMNet8&#xff0c;则说明安装成功。

内网穿透(cpolar实现)

目录 一、介绍 二、内网穿透工具cpolar实现 1.下载与安装 2.cpolar指定authtoken 3.获取临时域名 4.验证临时域名有效性 一、介绍 内网穿透&#xff0c;即 NAT 穿透&#xff08;Network Address Translation Traversal&#xff09;&#xff0c;是一种网络技术&#xff0…

Python爬虫使用实例-wallpaper

1/ 排雷避坑 &#x1f95d; 中文乱码问题 print(requests.get(urlurl,headersheaders).text)出现中文乱码 原因分析&#xff1a; <meta charset"gbk" />解决方法&#xff1a; 法一&#xff1a; response requests.get(urlurl,headersheaders) response.en…

java基础-IO(4)管道流 PipedOutputStream、PipedInputStream、PipedReader、PipedWriter

管道 提到管道&#xff0c;第一印象就是水管或者地下管道&#xff0c;一节接着一节&#xff0c;形如下图。 管道流 "流"从上一个管道 ——-> 下一个管道。又细分为字节流和字符流。 字节流&#xff08;PipedOutputStream、PipedInputStream&#xff09; 如果…

SSM框架介绍

SSM通常指的是三个开源框架的组合&#xff0c;即Spring、SpringMVC&#xff08;Spring Web MVC&#xff09;和MyBatis&#xff0c;这三个框架经常一起使用来开发Java企业级应用&#xff0c;特别是在Web应用开发中非常流行。 SSM框架介绍 Spring 简介&#xff1a;Spring是一个…

谷粒商城のNginx

文章目录 前言一、Nginx1、安装Nginx2、相关配置2.1、配置host2.2、配置Nginx2.3、配置网关 前言 本篇重点介绍项目中的Nginx配置。 一、Nginx 1、安装Nginx 首先需要在本地虚拟机执行&#xff1a; mkdir -p /mydata/nginx/html /mydata/nginx/logs /mydata/nginx/conf在项目…

ModuleNotFoundError: No module named ‘mmcv.transforms‘

不得已的解决方法&#xff1a; mmcv升级到2.0.0即可解决 升级后自然又面临一系列不兼容问题&#xff01; 官方文档查漏补缺

【STM32】呼吸灯实现

对应pwm概念可以去看我的博客51实现的呼吸灯 根据对应图我们可知预分频系数为999&#xff0c;重装载值为2000&#xff0c;因为设置内部时钟晶振频率为100MHZ &#xff0c;1s跳 100 000000次 &#xff0c;跳一次需要1/100 000000s 20ms0.02s 对应跳的次数为 我们使用通用定时器…

九,自定义转换器详细操作(附+详细源码解析)

九&#xff0c;自定义转换器详细操作&#xff08;附详细源码解析&#xff09; 文章目录 九&#xff0c;自定义转换器详细操作&#xff08;附详细源码解析&#xff09;1. 基本介绍2. 准备工作3. 自定义转换器操作4. 自定义转换器的注意事项和细节5. 总结&#xff1a;6. 最后&…

【前端学习】AntV G6-07 深入图形与图形分组、自定义节点、节点动画(上、中)

课程链接 AntV G6&#xff1a;深入图形与图形分组、自定义节点、节点动画&#xff08;上&#xff09;_哔哩哔哩_bilibili AntV G6&#xff1a;深入图形与图形分组、自定义节点、节点动画&#xff08;中&#xff09;_哔哩哔哩_bilibili 图形分组 Group | G6 (antgroup.com) 自…

ARM32开发——DMA内存到内存

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 需求数据交互流程开发流程依赖引入DMA初始DMA传输请求完整代码 关心的内容DMA初始化DMA初始化DMA数据传输请求完整代码 DMA中断开启…

封装智能指针 qt实现登录界面

1.封装独占智能指针——unique_ptr #include <iostream> #include <utility> // For std::move// 命名空间 namespace custom_memory { template <typename T> class myPtr { public:// 使用初始化列表进行初始化explicit myPtr(T* p nullptr) noexcept : …

卡西莫多的诗文集2022-2024.9月6-校庆国庆专版定版

通过网盘分享的文件&#xff1a;卡西莫多的诗文集2022-2024.9月6-A5-校庆国庆专版-定版.pdf 链接: https://pan.baidu.com/s/1cpFK5k1baGXbSGxY30GL_A?pwdjgnt 提取码: jgnt 卡西莫多的诗文集2022-2024.9月6-校庆国庆专版&#xff0c;又稍作修改并勘误了一些错字&#xff0c;…

AWQ量化(Activation-aware Weight Quantization)

论文&#xff1a; AWQ: Activation-aware Weight Quantization for On-Device LLM Compression and Acceleration 中文解读&#xff1a; 深入理解AWQ量化技术 - 知乎 (zhihu.com) 动机&#xff1a;端侧设备用LLM&#xff0c;为了减少显存占用量&#xff0c;所以要用INT4量化&am…