JavaEE初阶 多线程(初阶)

文章目录

  • 1. 进程
  • 2. 认识线程(Thread)
    • 1.1 概念
    • 1.2 第一个多线程程序

1. 进程

程序 : 指的是计算机中的一些exe可执行文件 (硬盘中)把程序跑起来 才会涉及到"进程"

进程 : 如果双击程序 此时操作系统就会把可执行文件中的数据和指令加载到内存中 将程序执行后运行中叫做程序 进程又叫做程序的实例化

JavaEE初阶 多线程(初阶)_第1张图片
进程与程序的关系
进程要运行 说明消耗一定的硬件资源
会消耗 cpu资源 内存 硬盘 网络宽带…
程序是静态 只占用了硬盘资源
在这里插入图片描述
假设下载了一个病毒没有安装是否会中病毒??
不会 下载的是一个程序 是静态的(没法执行任何操作)
安装则是真正运行起来成为进程了

进程是系统分配硬件资源的基本单位
针对计算机的进程的管理
描述: 会使用一个专门的结构体(双向链表)来记录一个进程里面的各个属性
PCB(进程控制块Processing Control Block)
查看进程的列表 本质上就是在遍历这个链表
创建一个进程 就是创建了一个pcb结构体 并插入到链表上
销毁一个进程 就是把pcb结构体从连表示删除并释放
PCB中大概有那些信息?
1. pid进程的标识
同一个系统上 同一时刻 每个进程的pid一定都是不同的
JavaEE初阶 多线程(初阶)_第2张图片
2. 内存指针
表示了该进程 对应的内存资源 是咋样的
内存资源中要存啥?
最主要的是存储的就是从exe可执行文件加载过来的指令数据

3. 文件描述符表
和硬盘资源有关
银盘是硬件应用程序一般是无法直接接触到"硬件"这一层 实际上是操作系统抽象成"文件"这样的概念 程序操作文件 文件实际上是存储在硬盘上的 每一个进程就会有一个"文件描述符表"来记录 当前中国进程正在使用那些文件

组织: 会使用一系列的数据结构 把多个进程进行一个有效的组织 随时方便进行遍历 查找 汇总数据

在一个计算机上有许多进程 进程需要在cpu上执行指令
一台机器只有一个cpu 所以要想运行进程是轮流使用的
cup的调度

  1. 并行: 同一时刻 两个进程 同时运行在cpu逻辑核心上
  2. 并发: 两个进程 轮流执行 由于cpu切换进程速度极快 微观上 两个进程是串行执行的 宏观上 两个进程就是同时执行
    在这里插入图片描述
    在这里插入图片描述
    pcb中关于进程调度相关的属性
    描述了进程对应的cpu资源的使用情况
  1. 状态
    状态可以相互转换
    就绪状态 一个进程已经随时做好在cpu上执行的准备
    阻塞状态/睡眠状态
  2. 优先级
    系统给进程进行调度的时候 不是完全公平的 会根据优先级不同 来决定时间分配的权衡
  3. 上下文
    进程都是轮流进行 一次运行不完 就需要保证下次cpu上运行的时候 能从上次运行到的位置 继续往后运行 存档读档
  4. 记帐信息
    相当于是一个统计信息 会统计每个进程在cpu上执行了多久 执行了多少指令

每个进程 都需要一定的内存资源
早期的操作系统 就是直接把物理内存分配给进程 就会带来一个严重的问题 一旦某个内部代码写出bug 内存越界访问了 就可能会影响到 别的进程
按照上诉 直接分配物理内存的模型 此时一旦指针越界指向其他位置 就很可能影响到别的进程的执行 后来操作系统就引用了虚拟地址空间
JavaEE初阶 多线程(初阶)_第3张图片
这样设定之后 每个进程的有效的虚拟地址 都是固定范围 进程使用该虚拟地址的内存 都是需要操作系统进行转换成物理地址的过程 中国转换过程中 就可以针对 虚拟地址是否有效 做出一个 校验
在虚拟地址空间的加持下 => 进程就具有了"独立性" 每个进程都有自己的虚拟地址空间 一个进程无法直接访问或者修改其他进程虚拟地址空间的内容 强化了系统的稳定性

通过虚拟地址空间 把进程隔离开了 但是有时候 还要让进程之间 产生点配合 进程间通信 就是在进程隔离性的基础上 开个口子 能够有限制的进行相互影响

2. 认识线程(Thread)

多进程已经很好实现 并发编程的效果
但是进程有明显的缺点 : 进程太重了

  1. 消耗资源更多
  2. 速度更慢
    进程大规模的创建和销毁频繁
    开销十分大
    能不能创建进程的时候 只分配一个简单的pcb而不去分配后续的这些内存硬盘资源 是不是就更快了 这样就能并发执行任务 又能提升创建/销毁的速度

轻量级 进程 => 线程(只创建了一个pcb 但是没有分配后续的内存硬盘…资源)

创建的还是进程 创建进程的时候 把资源分配好
后续创建线程 让线程在进程内部 (进程和线程之间的关系 可以认为是 进程包含了线程)
后续程序中新的线程 直接复用前面进程这里创建好的资源
其实一个进程 至少要包含一个线程 后面继续将建线程就可以省略分配资源的过程 资源已经是有了的
线程和进程的关系
例如
JavaEE初阶 多线程(初阶)_第4张图片
JavaEE初阶 多线程(初阶)_第5张图片

1.1 概念

  1. 线程是什么?
    一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码.
  2. 线程同样也是通过PCB结构体来描述的
    JavaEE初阶 多线程(初阶)_第6张图片
    此时一个PCB对应一个线程 可能有多个PCB对应一个进程
    PCB中的内存指针 文件描述符表 同一个进程的多个PCB中 这两字段的内容都是一样的但是上下文 状态 优先级 记账信息 支持调度的属性 则每个pcb都不一样(同一个进程中的这些线程共用同一份资源 内存+硬盘)
    但是每个线程独立区cpu上调度(状态 上下文 优先级 记账信息 各自有各自的一份)

进程,是操作系统进行资源分配的基本单位
线程,是操作系统进行调度执行的基本单位
例如
要吃100只鸡
单进程模式
JavaEE初阶 多线程(初阶)_第7张图片
多进程模式
优点 效率提高
缺点 分配更多资源
JavaEE初阶 多线程(初阶)_第8张图片
多线程模式
创建线程 比创建进程更轻量
JavaEE初阶 多线程(初阶)_第9张图片
但是一个进程中能创建的线程是有限的
一个进程只能创建固定的线程(cpu的逻辑核心数)

JavaEE初阶 多线程(初阶)_第10张图片
一个线程在执行过程中出现异常 并且这个异常没有很好的被处理很有可能会导致这个进程直接中止而多进程没有这个顾虑 一个是更轻量 一个是更稳定

面试题(高频)
进程和线程的区别和联系

  1. 进程包含线程 都是为了实现并发编程的方式 线程比进程更轻量
  2. 进程是系统分配资源的基本单位 线程是系统调度执行的基本单位 创建进程的时候把分配资源(虚拟空间地址(内存) 文件描述符表(硬盘))的工作给做了 后续创建线程 直接共用之前的资源即可
  3. 进程有独立的地址空间 彼此之间不会相互影响到 进程的独立性 系统稳点性
    多个线程共用一份地址空间 一个线程一旦抛出异常 就可能会导致异常结束 多个线程之间容易相互影响

Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库)
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装

1.2 第一个多线程程序

package thread;
class MyThread extends Thread{
    @Override
    //这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
    public void run(){
        System.out.println("hello world");
    }
}
//Thread 类来自java.lang这个包 不需要import就可以直接用
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//此处不仅仅要创建出类 还需要调用它让它执行起来
        myThread.start();
        //此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
        //此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
    }
}

Thread 类来自java.lang这个包 不需要import就可以直接用

这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的

此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中

此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
JavaEE初阶 多线程(初阶)_第11张图片
当我们这样写的时候
JavaEE初阶 多线程(初阶)_第12张图片
run只是上面的入口方法(普通方法) 并没有调用系统api 也没有创建出真正的线程来

当点击运行程序的时候就会先创建出一个java进程这个进程中就包含了至少一个线程 这个线程也叫做主线程 也就是负责执行main方法的线程

当我们调整代码之后 在main方法中有一个while循环 在线程中run也有一个while循环
使用start的方式执行 此时这俩循环都在执行
JavaEE初阶 多线程(初阶)_第13张图片
JavaEE初阶 多线程(初阶)_第14张图片
两个线程分别执行之间的循环 这两个线程都能参与cpu的调度中以并发式执行

如果将代码改为
JavaEE初阶 多线程(初阶)_第15张图片
它只创建了一个线程 只有当run方法执行完成之后才会执行下面的代码 所以输出结果只有 hello world
JavaEE初阶 多线程(初阶)_第16张图片
总结
JavaEE初阶 多线程(初阶)_第17张图片

JavaEE初阶 多线程(初阶)_第18张图片
我们可以使用 jconsole查看线程
JavaEE初阶 多线程(初阶)_第19张图片
JavaEE初阶 多线程(初阶)_第20张图片
可以在jdk的路径下寻找
JavaEE初阶 多线程(初阶)_第21张图片
JavaEE初阶 多线程(初阶)_第22张图片
然后我们启动java程序
选择
JavaEE初阶 多线程(初阶)_第23张图片
能够列出系统上正在运行的所有的java进程
JavaEE初阶 多线程(初阶)_第24张图片
线程的调用栈
JavaEE初阶 多线程(初阶)_第25张图片
JavaEE初阶 多线程(初阶)_第26张图片
线程的调用栈 方法之间的调用关系
尤其是 当程序卡死了 查看一下 这里每个线程的调用栈 就可以知道大概那个代码出现卡死情况

Tread.sleep
JavaEE初阶 多线程(初阶)_第27张图片
JavaEE初阶 多线程(初阶)_第28张图片
JavaEE初阶 多线程(初阶)_第29张图片
Tread.sleep 时间是毫秒
运行结果并非是严格的交替
这两线程在进行sleep之后就会进入阻塞状态
当时间到之后 系统就会唤醒这两线程 回复线程的调度
谁先调度 可以认为是"随机"的
这样"随机"的调度过程 称位"抢占式执行"
JavaEE初阶 多线程(初阶)_第30张图片
Java中 通过Thread 类创建线程的方式还要很多种写法

  1. 创建一个类 继承 (class)Thread 重写run方法
  2. 创建一个类 实现(interface)Runnable 重写run方法 形容词性able结尾
    package thread;
    //使用Runable 的方式创建线程
class MyRunnable implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("hello world");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        while (true){
            System.out.println("hellw main");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

JavaEE初阶 多线程(初阶)_第31张图片
Thread 这里是直接把要完成的工作放到了Thread的run方法中
Runnable 这里 则分开了 要把完成的工作放到Runnable 中再让Runnable 和Thread配合
JavaEE初阶 多线程(初阶)_第32张图片

你可能感兴趣的:(java-ee,linux)