多线程(初识线程)

news2025/7/13 8:02:54

线程的诞生

了解进程存在的意义

实现了并发编程的效果(并发编程:有可能是并发执行,也有可能是并行执行)

并发编程的目的:充分利用上多核CPU资源,提升运行效率


了解进程创建和销毁的过程带来的问题

进程是如何创建的:

1、创建PCB

2、给进程分配资源(内存/文件)赋值到PCB中

3、将PCB插入到链表中。

进程是如何销毁的:

1、把PCB从链表从删除;

2、把PCB中持有的资源释放

3、销毁PCB;

虽然多进程已经实现了并发编程,但是存在重要的问题:

如果频繁的创建/销毁进程,这个操作就比较低效

为什么这么说?

分配资源这个事情,对操作系统来说是一个比较大的事情,非常消耗时间

因为上述原因聪明的程序员发明了“线程”;

认识线程

线程是包含在进程中的。
一个进程默认会有一个线程(主线程),当然也可以有多个线程;
每个线程都是一个“执行流”可以单独在CPU上进行调度;
同一个进程中的这些线程,共用同一份系统资源(内存 + 文件)

Tips:一个进程至少有一个线程,也可以有多个;

线程 : 可以理解为"轻量级进程“
优势:

1、创建线程的开销比进程小

2、销毁线程的开销比销毁进程小

进程和线程的关系

前面有说到过,操作系统内核是通过PCB来描述进程的,更准正确的说法是,一组PCB来描述一个进程,每个PCB对应一个线程。
这一组PCB上的内存指针,和文件描述符表其实是同一份东西,而状态,上下文,优先级,记账信息,则是每个PCB(每个线程)自己有一份

进程是资源分配的基本单位
线程是调度执行的基本单位

使用多线程的目的

1、能够充分利用上多核CPU,能够提高效率

2、只是创建第一个线程的时候,需要申请资源,后续再创建新的线程,都是共用同一份资源(节省了资源申请的开销)销毁线程的时候,也只是销毁到最后一个的时候,才真正释放资 源,前面的线程销毁,都不必真释放资源

面试题:线程和进程之间的区别(谈到操作系统,线程进程的概念和区别必考)

1、进程包含线程

2、线程比进程更加轻量,创建更快,销毁也更快

3、同一个进程下的多个线程之间共用同一份资源(内存 + 文件),进程和进程之间则是独立的内存/文件资源

4、进程是系统分配资源的基本单位;线程是调度执行的基本单位

多线程存在的问题

1、线程数目不是越多越好

例如:临近寒假结束开学之际,小明的寒假作业有7本且一字未动,如果小明自己一个人写7本,是不是需要的时间很久?此时如果有3个人一起帮忙写,那是不是效率一下子就提高了,如果此时增加到7人,每人一本正正好好,那如果此时人有14个人,每个人都很热情想帮小明,这个人写一下那个人想抢过来帮一下忙写一下,此时这样反复的争抢也没有意义,根本没有办法好好写作业。

对应到线程是不是也是这样?线程数目也不是越多越好,CPU核心数是有限的,当线程数目达到一定程度的时候,CPU核心数就已经吃满了!此时继续增加也无法在提高效率,反而会因为线程田铎,线程调度开销太大影响了效率。

2、线程之间会互相影响

7个人7本作业,一人拿一本时候是不是特别合适?如果有两个人同时想拿走同一本他们比较熟悉的作业,那就回抢起来。

1、对比到线程也是一样的,如果两个线程修改同一个变量,也容易产生“线程不安全问题”。

如果7本作业,其他6个人6本都写的好好的,突然有一个人偷懒不想写,那检查作业时候是不是就会因为这一本而被判定没有好好完成作业。

2、对应到线程,如果某个线程运行过程中出现异常,并且异常没有处理好,整个进程都会随之崩溃!整个时候后续其他线程自然难以运行。

创建线程的方式

1、继承Thread类,重写run

image-20230221193056935

image-20230221193137355

解释代码:

Thread类:标准库中提供了一个Thread类,使用的时候 就可以继承整个类 (Thread类相当于Java对操作系统中的线程进行封装)

run方法:重写父类里面的run方法,里面的逻辑就是整个线程要执行的工作~创建子类并且重写run方法,相当于“安排任务“

在main方法中(主线程)创建实例 (MyThread thread = new MyThread();)并不会在系统中真正创建一个线程!
调用start()方法的时候,才会真正创建出一个新的线程~
新的线程就会执行run里面的逻辑,直到run里的代码执行完

注意:在这个代码中只有一个进程;main方法相当于这个进程的(默认)主线程,相当于线程的入口方法,我们调用的Mythread的线程相当于第二个线程,run就是这个新线程的入口方法,主线程执行完进程并不会销毁,而是等待所有进程执行完才销毁。

2、实现Runnable接口,重写run

image-20230221194929236

image-20230221194937606

解释代码:

在MyRunnable类里面实现Runnable接口,重写run方法,run方法里面就是这个线程要执行的任务

main方法里面的代码如图所示,解释下面这段代码:

image-20230221195124928

优势1:这样可以解耦合把线程要干的活和线程本身分开了,使用Runnable来专门表示“线程要完成的工作”

把任务提取出来的目的就是为了解耦合,第一种创建线程的写法就把线程要完成的工作和线程本身耦合在一起了。

耦合高是不好的,例如:

未来要对这个代码进行调整,不再使用多线程了,用其他方式,代码改动就比较大,而Runnable这种写法就只需要把Runnable传给新方式的实体即可。

优势2:如果想搞多个线程,都干一样的活,这个时候也适合使用Runnable的。

image-20230221195614291

3、使用匿名内部类,实现创建Thread子类的方式

image-20230221200037810

 Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("线程完成执行的任务");
            }
        };

以上代码相当于创建了一个匿名的Thread的子类,同时实例化出一个对象。

4、匿名内部类创建实现 Runnable 子类对象

image-20230221203348304

Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("该线程执行的任务");
            }
        });

new Runnable(){}匿名内部类的实例,作为构造方法的参数

5、lambda 表达式创建 Runnable 子类对象

image-20230221204348691

这里的lambda实际上就是第4中方式的简化版。

关于lambda表达式不过多赘述。

面试题:Java中有哪些方式能创建线程?

除了以上五种还有其他的方式后面的文章详细解答。

验证多线程的优势

前面一直说多线程能够充分利用多核CPU,提高程序的效率,利用代码直观的展现出来看看。

使用单线程和多线程的方式分别计算从0到100_0000_0000需要多长时间

1、串行执行:

 // 串行执行任务
    public static void serial() {
        // 记录 ms 级别的时间戳.
        long beg = System.currentTimeMillis();

        long a = 0;
        for (long i = 0; i < COUNT; i++) {
            a++;
        }

        a = 0;
        for (long i = 0; i < COUNT; i++) {
            a++;
        }

        long end = System.currentTimeMillis();
        System.out.println("执行的时间间隔: " + (end - beg) + " ms");
    }

2、并发执行

// 并发执行任务
    public static void concurrency() throws InterruptedException {
        long beg = System.currentTimeMillis();
        Thread t1 = new Thread(()->{
            long a = 0;
            for(int i = 0; i < COUNT; i++) {
                a++;
            }
        });
        Thread t2 = new Thread(()->{
            long a = 0;
            for(int i = 0; i < COUNT; i++) {
                a++;
            }
        });
        t1.start();
        t2.start();

        //等待t1,t2执行完以后再结算结束时间
        t1.join();
        t2.join();

        long end = System.currentTimeMillis();
        System.out.println("执行的时间间隔: " + (end - beg) + " ms");


    }

为什么要加上 t1.start(); t2.start();两个代码?

原因就是因为main线程,t1,t2线程三个线程是分别独立执行。

当main执行完t1,t2之后,仍然会继续往后走!如果t1,t2还没执行完,就计算结束时间,这是不合理的,所以要让main线程等差t1和t2执行完了才能够停止计时。

join:阻塞等待,在main中调用t1.join效果就是让main线程阻塞一直到t1线程执行完run方法,main才继续执行。

t1和t2是并发执行,而不是先执行完t1才执行t2

串行和并行执行的时间分别为:

image-20230221214232818

结论:

使用两个线程,最终消耗的时间,不一定是一个线程消耗时间的50%;

一个线程串行执行:610ms左右;
两个线程并发执行:369ms左右;

确实两个线程并发执行快了很多,但是不是正好50%;

原因如下:

1、并发执行 = 微观上的并行 + 并发

其中并行执行确实会提高程序的执行速度,并发执行反而会因为一下子执行a线程,一下子执行b线程这样的来回调度,反而因为调度开销让时间增加

问题就在于一次程序运行有多少次是并行执行还有多少次是并发执行我们是不清楚的

2、包括创建线程的实例,也是有开销的.
串行执行,没有额外创建线程
并发执行,额外创建两个线程

总结:

如果计算量大,计算的久,创建线程的开销就更不明显 (忽略不计)
如果计算量小,计算的快,创建线程的开销影响更大,多线程的提升,就更不明显.
这个时候线程的调度/创建销毁都有影响

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

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

相关文章

系列二、函数

一、定义 函数 是指一段可以直接被另一段程序调用的程序或代码。 也就意味着&#xff0c;这一段程序或代码MySQL中 已经给我们提供了&#xff0c;我们要做的就是在合适的业务场景调用对应的函数完成对应的业务需求即可。二、字符串函数 2.1、案例 2.1.1、concat 字符串拼接 s…

js中?.、??的具体用法

1、?. &#xff08;可选链运算符&#xff09; 在javascript中如果一个值为null、undefined&#xff0c;直接访问下面的属性&#xff0c;会报 Uncaught TypeError: Cannot read properties of undefined 异常错误。而在真实的项目中是会出现这种情况&#xff0c;有这个值就读这…

泛型擦除(Generic erase)(内含教学视频+源代码)

泛型擦除&#xff08;Generic erase&#xff09;&#xff08;内含教学视频源代码&#xff09; 教学视频源代码下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87473560 源代码中使用的泛型&#xff0c;在经过编辑后&#xff0c;代码中就看不到泛…

PX4之飞行控制框架

PX4的飞行控制程序通过模块来实现&#xff0c;与飞控相关的模块主要有commander&#xff0c;navigator&#xff0c;pos_control&#xff0c;att_control这几个&#xff0c;分别可以在src/modules目录中找到。 commander - 指令/事件处理模块&#xff0c;处理指令、遥控器输入和…

新C++(9):谈谈,翻转那些事儿

"相信羁绊&#xff0c;相信微光&#xff0c;相信一切无常。"一、AVL树翻转那些事儿(1)什么是AVL树&#xff1f;在计算机科学中&#xff0c;AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1&#xff0c;所以它也被称为高度平衡树。…

网上插画教学哪家质量好,汇总5大插画培训班

网上插画教学哪家质量好&#xff1f;给大家梳理了国内5家专业的插画师培训班&#xff0c;最新五大插画班排行榜&#xff0c;各有优势和特色&#xff01; 一&#xff1a;国内知名插画培训机构排名 1、轻微课&#xff08;五颗星&#xff09; 主打课程有日系插画、游戏原画、古风插…

Tencent OS下逻辑卷(LVM)创建和扩容

测试环境是一个虚拟机&#xff0c;原配置1个虚拟盘。 创建4个虚拟盘&#xff0c;每盘2G并挂载在虚拟主机上&#xff0c;启动虚拟主机开始测试。 LVM英文是Logical Volume Manager&#xff0c;直接翻译为逻辑卷管理。 这种磁盘管理模式比较灵活&#xff0c;在磁盘空间不足的时…

深入浅出C++ ——容器适配器

文章目录一、容器适配器二、deque类简介1. deque的原理2. deque迭代器3. deque的优点和缺陷4. 为什么选择deque作为stack和queue的底层默认容器一、容器适配器 适配器的概念 适配器是STL六大核心组件之一&#xff0c;它是一种设计模式&#xff0c;该种模式是将一个类的接口转换…

大规模 IoT 边缘容器集群管理的几种架构-2-HashiCorp 解决方案 Nomad

前文回顾 大规模 IoT 边缘容器集群管理的几种架构-0-边缘容器及架构简介大规模 IoT 边缘容器集群管理的几种架构-1-RancherK3s &#x1f4da;️Reference: IoT 边缘计算系列文章 HashiCorp 解决方案 - Nomad Docker 简介 Nomad: 一个简单而灵活的调度器和编排器&#xff0c;…

网络工程课(二)

ensp配置vlan 一、配置计算机ip地址和子网掩码 二、配置交换机LSW1 system-view [Huawei]sysname SW1 [SW1]vlan batch 10 20 [SW1]interface Ethernet0/0/1 [SW1-Ethernet0/0/1]port link-type access 将接口设为access接口 [SW1-Ethernet0/0/1]port default vlan 10 [SW1-E…

【MyBatis】源码学习 04 - 从 MapperMethod 简单分析一条 SQL 的映射操作流程

文章目录前言参考目录学习笔记1、测试代码说明2、binding 包的主要功能3、获取 Mapper 接口实例过程4、SQL 语句执行流程4.1、方法调用器4.2、MapperMethod 绑定方法4.2.1、SqlCommand4.2.2、MethodSignature4.3、MapperMethod#execute前言 本文内容对应的是书本第 13 章的内容…

【亲测2022年】网络工程师被问最多的面试笔试题

嗨罗~大家好久不见&#xff0c;主要是薄荷呢主业还是比较繁忙的啦&#xff0c;之前发了一个面试题大家都很喜欢&#xff0c;非常感谢各位大佬对薄荷的喜爱&#xff0c;嘻嘻然后呢~薄荷调研了身边的朋友和同事&#xff0c;发现我们之前去面试&#xff0c;写的面试题有很多共同的…

C++ Effictive 第6章 继承与面向对象设计 笔记

继承意味着"is-a"。如果B继承自A&#xff0c;那么B is-a A。 子类声明与父类函数同名的函数时&#xff0c;父类函数会被遮掩。 使用using Base::func(args...)&#xff1b;父类所有func的重载函数都在子类中被声明。此举下&#xff0c;如果子类函数与父类函数参数也一…

不要对chatgpt过度反思 第一部分

最近一段时间&#xff0c;chatgpt很热&#xff0c;随意翻一些文章或视频&#xff0c;一些非常整齐一致的怪论&#xff0c;时不时都会冒出来。 为什么这种革命性创新又出现美国&#xff1f; 为什么我国互联网只会电商&#xff0c;没有创新&#xff1f; 为什么我们做不出来&…

列表推导式_Python教程

内容摘要 Python中存在一种特殊的表达式&#xff0c;名为推导式&#xff0c;它的作用是将一种数据结构作为输入&#xff0c;再经过过滤计算等处理&#xff0c;最后输出另一种数据结构。根据数据结构的不同会被分为列表推导式、 文章正文 Python中存在一种特殊的表达式&#x…

股票、指数、快照、逐笔... 不同行情数据源的实时关联分析应用

在进行数据分析时经常需要对多个不同的数据源进行关联操作&#xff0c;因此在各类数据库的 SQL 语言中均包含了丰富的 join 语句&#xff0c;以支持批计算中的多种关联操作。 DolphinDB 不仅通过 join 语法支持了对于全量历史数据的关联处理&#xff0c;而且在要求低延时的实时…

Qt信号与槽使用方法总结

前言 在图形界面编程中QT是为首选&#xff0c;组件之间如何实现通信是核心的技术内容。Qt 使用了信号与槽的机制&#xff0c;非常的高效、简单、易学&#xff0c;方便开发者的使用。本文详细的介绍了Qt 当中信号与槽的概念&#xff0c;并演示了各种信号与槽的连接方式。 什么…

你知道 GO 中的 协程可以无止境的开吗?

GO语言天生高并发的语言&#xff0c;那么是不是使用 go 开辟协程越多越好的&#xff0c;那么在 go 里面&#xff0c;协程是不是可以开无限多个呢&#xff1f; 那么我们就一起来看看尝试写写 demo 吧 尝试开辟尽可能多的 协程 写一个 demo &#xff0c;循环开 1 << 31 …

自由变化,功能增强,适配优化—V6.0.2版本发布

本次更新&#xff1a;经过两个月的细节打磨&#xff0c; V6.0.2版本发布&#xff0c;自由变化&#xff0c;功能增强&#xff0c;适配优化&#xff1b;新版本增加了超级弹窗&#xff0c;可以多窗口并存&#xff1b;增加了编号组件&#xff0c;可以调用编号组件库&#xff0c;自动…

Artiifact分析HSV数据

Artiifact分析HSV数据1 下载Artiifact分析工具2 安装软件后打开软件3 分析1 Extract IBIS from ECG data2 Detect and process artifact in IBI data3 Analse HRVARTiiFACT&#xff0c;这是一种用于处理心电图和 IBI 数据的软件工具。图形用户界面中提供了自动和手动伪影检测和…