多线程(1)

多线程


前言 : 上文主要了解到了进程, 那么为啥需要引入进程呢?

或者说为啥要有进程呢?

其实这里最主要的目的是为了解决 并发编程 这样的问题。


了解 :

这里 cpu 进入了多核心的时代,想要进一步提高程序的执行速度,就需要充分的利用CPU的多核心资源.

cpu 再往小了做 就比较困难 ,此时先要通过单核心提高执行速度就遇到了困难, 既然一个核心不行那么多个核心呢?显然是可以的,要不一句话人多力量大吗。

但是不是说 cpu的核心多了,程序一下就跑的快了 , 还需要你的程序代码能供把这些 cpu 核心给用上 。

引出多线程

回到进程这里, 关于我们的多进程编程,已经是可以解决并发编程的问题(可以利用cpu的多核资源) ,但是因为频繁的创建和销毁进程对cpu的资源开销是比较大的 ,调度进程同样也是 ,另外 使用进程会拖慢了我们的运行速度 。

正因为 使用 进程, 开销比较大速度比较慢 , 为了解决这个问题 我们的线程就应运而生 , 这里线程也称为 轻量级进程

那么为啥说进程的开销比较大呢 ?

这里主要就在 资源分配 / 回收上 , 之前说过 进程是分配资源的最小单位, 我们想要创建进程就需要给它分配资源 (内存 资源, 硬盘资源等), 此时分配资源就需要花费一定的时间.


那么为啥说线程的开销小速度快呢 ?

其实也就是线程将分配资源/ 回收上 / (申请资源和回收资源) 的这一部分开销给省略了


为了好理解 这里举一个例子 :


那种流水线的厂子不知道大家进没进过,鄙人有幸在里面做过一天的事情,可以说是非常的累的 。

假设 有一个厂是生产手机的 ,一天能生产 1w部手机, 但是市场上需求量非常高,导致一天生产 1w部手机可能不够,此时厂长就想要提高产量,让每天有 2 w 部手机产出 ,这里就有两种解决方式

1.在其他的方法重新开一个厂,然后两个厂同时生产那么此时是不是一天就可以达到2w部,


2.既然是流水线,那么是不是可以多增加一条流水线,此时是不是同样可以使 一天 2 w 部 (假设之前的厂只有一条流水线,但大部分不可能是这个情况)


对比一下这两种方法,你觉的是 1 好 还是 2 好 显然是 2 好,因为重新开一个厂 ,需要挑选场地, 建厂, 装流水线等都是需要花钱的,而 方法二 新增一条流水线 ,就不需要花额外的钱去 挑选场地,建厂 。


这里是不是就可以认为 方法一 是使用进程 ,而方法二是线程呢?


既然将 进程 想象成 厂 , 线程想象程流水线, 这里就可以得出 进程和线程之前的关系 。

一个进程可以包含一个线程,也可以包含多个线程(注意 : 这里不能没有)

好理解 : 厂吗不可能没有流水线的,要不然咋生产商品赚钱呢 做慈善吗, 同样厂可以拥有一条,也可以拥有多条.

另外 : 正因为我们的厂内可以拥有多条流水线,那么是不是只有创建厂和新建第一条流水线的时候开销比较大,之后添加的流水线,就直接在厂内添加即可? 放到进程和线程上来说,也就是我们启动第一个线程的时候开销是比较大的 去申请 资源 (没有资源咋办事吗),后面的进程就省事了, 因为是不会去申请和释放资源的直接用即可.


通过上面总结 : 一个进程可以包含多个线程程 , 这些线程之间共用同一份资源


补充: 这里的资源指的是 内存 和 文件描述符表

内存 : 线程1 new 的对象 在 线程 2 , 3 , 4 等 都可以直接使用

文件描述符表 : 线程 1 打开的文件 , 在线程 2 , 3 , 4 等轴都可以直接使用 .

另外 : 操作实际调度的时候,是以线程为单位进行调度的.

回到上文 所说的进程调度就不够准确, 准确来说是以线程为单位来调度的 , 如果站在上文的角度 说进程调度就相当于每一个进程只有一个线程的情况.

补充 : 这里每个进程中的线程都是独立在CPU上进行调度的 , 换句话来说 线程是操作系统调度执行的基本单位


另外 : 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码.


看完上面, 有没有想过 我们要如何去描述一个线程呢 ?

在上文 介绍的 进程 通过 PCB来描述的,那么线程是不是也是呢 ?

答案 : 我们的线程同样也是通过PCB来描述的.


注意 :这里别晕,其实PCB对应的就是线程,为啥上文说是对应进程呢?因为是站在一个进程只有一个线程的角度,所以才会所对应PCB


此时就有趣了,既然 线程也是通过 PCB 来描述的,那么进程又可以拥有多个线程,那么进程是不是会对应多个PCB呢 ?


没错 我们的进程 是可能对应多个PCB的,正因线程同样也是被PCB描述的,进程中又含有多个线程所以,进程就会对应多个PCB了,

在这里插入图片描述

那么还有一个问题 :上文介绍 过一对PCB的属性 状态 ,上下文, 优先级 ,记账信息,是每个线程都有自己的还是共同使用一个呢 ?

答案 : 每个线程都会拥有自己的各自记录各自的,但是 同一进程里的PCB之间, pid是一样的,内存指针和文件描述符表也是一样的.


描述 看完 , 再来像一个问题 , 既然我们使用多线程 比较轻量 ,能提高效率,那么我们能不能一直创建线程呢?

答案 不是的 ,虽然创建线程是能提高我们的 效率但是,线程一多,会导致多个线程进行资源的竞争,此时肯定会有没有抢到的线程(资源就那么一份),此时就会拖慢我们的效率。


举例 :

图一 :

在这里插入图片描述


图二 :

在这里插入图片描述


总结 : 进程和线程的区别 (注意 :这里是非常经典的面试题)


1.进程是包含线程的 , 一个进程可以含有多个线程

2.进程和线程都是处理并发编程的场景

3.进程比较重 , 线程比较轻 .

为啥进程重 因为频繁的创建和消费进程的时候效率是非常低的(消耗的资源多) , 而线程就不会 ,

为啥线程轻 因为只有启动第一个线程的时候才会去创建和消耗资源,其他的线程是公用同一份资源的。

4.进程是具有独立性的, 每一个进程之间都拥有各自的虚拟地址空间,一个进程挂了,其他进程是不受影响的,而线程则不会,因为线程之间公用同一块资源,所以当其中一个线程出现了异常就可能导致整个进程崩溃

5.进程是操作系统分配资源的基本单位

6.线程是操作系统调度的执行的基本单位


理论知识 看完,下面来看我们java如何进行多线程操作

Thread

引用 :

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.


下面我们就通过 Thread 来创建我们的线程。


第一步 : 打开idea 创建类
在这里插入图片描述


第二步 : 创建 Thread 对象

在这里插入图片描述


问题 一: 为啥使用 Thread 类不需要通过 import 导入别的 包 ?

问题二 :你还见过那些类也是类似的.


答: 我们之前见过的String , StringBuilder ,StringBuffer 都是这样的,因为他们都是在java.lang底下的,因为java.lang 编译器会自动给我们导入.

问题 看完了, 回到上面,其实这里的线程还是没有创建完成,因为我们创建线程是希望线程成为一个独立的执行流(执行一段代码),上面的程序并没有给他执行的任务。

那么这里的问题就来到了如何指定他的任务呢 ?

这里总共有 5种方法,下面就来看看


方法一 : 实现一个类继承Thread 重写run方法 .

在这里插入图片描述


run方法里面就写我们需要执行的任务 .


下面就来执行我们的程序看一下效果 :

在这里插入图片描述


注意 : 只有我们 通过 s.start() 去启动线程的时候才会创建线程, 而 new 是不会创建线程的 .

之前说过我们的线程是用来解决并发编程的,上面并没有体现出来,下面我们来修改一下我们的代码,来看看如何并发的。

在这里插入图片描述


因为这里执行的非常快我们就可以通过 sleep 让线程休眠一秒来看执行效果

在这里插入图片描述


正因为 抢占式执行,才会出现我们的线程安全问题 . 随机调度导致多个线程竞争同一份资源.


另外在来补充一下 t.runt.start 的区别

在这里插入图片描述

扩充 : 通过 jdk 自带的工具jconsole 查看当前的 java 进程中的所有进程

在这里插入图片描述

图二 : 观察线程

在这里插入图片描述

图三 :

在这里插入图片描述

此时第一种写法就完成 了 ,下面来看第二种 :


方法二 : 实现Runnable 接口

在这里插入图片描述

这里的Runnable 作用 是描述 一个要执行的任务 , run 方法是任务的执行细节.

这里我们不能直接通过 new Runnable (接口不能实例化) 传给Thread 来执行 , 所以需要去实现一个类 MyRunnable ,然后通过 new 传给 Thread类.

可以看到, 我们的任务是 通过 Runnable 描述的,我们将任务传给了线程, 这里就 将线程 和 线程需要干的活进行了解耦合操作.

这里的好处 就是未来的莫一天,我们要改代码,不用多线程了,使用多进程, 或者线程池或者协程等 此时代码的改动就比较小.


方法三 : 匿名内部类 继承Thread 重写 run方法

知识点 : 《内部类》

在这里插入图片描述


这里的方法三其实就是方法一,只不过这里使用了匿名内部类


方法四 : 匿名内部类 实现 Runnable接口 ,重写 run方法

在这里插入图片描述


这个写法与 写法2 等价 ,只不过实现 Runnable 的是一个匿名内部内 .


方法五 : 使用Lambda 表达式

知识点 : 反射 - 枚举 - Lambda表达式

在这里插入图片描述

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值