目录
- //进程是操作系统对一个正在运行的程序的一种抽象,通俗的说,进程就是运行起来的程序;
- //同一时刻系统中的进程会有很多,而操作系统就得给他们安排好
- //就是说: 进程实时操作系统分配资源的基本单位
- //进程管理 = 描述 + 组织
- //描述:详细的表示一个进程中有哪些属性/信息,而结构体就是用来描述的;
- //一个结构体对象对应一个进程 结构体也叫PCB(进程控制块)
- //组织:通过一定的数据结构,将若干个用来描述的实体组织到一起,进行增删改查;
- //系统通常会使用 双向链表 这样的结构来将 PCB 组织起来
- //
创建一个进程,本质上就是创建PCB,给进程分配资源赋值到PCB中,并且加入到链表上;
销毁一个进程,本质上就是从链表上删除对应的PCB节点,是否PCB持有资源
查看任务管理器的进程列表,本质上就是遍历这个链表
一个进程也可以是多个PCB ,系统管理PCB的链表也可以是多个
1. PID 进程的身份表示
一个主机,同一时刻,进程的PID是唯一的(相当于身份证号码),可以通过PID区分进程
2. 内存指针:描述了这个进程使用的内存空间是那个范围(分配空间范围)
3. 文件描述符:描述了这个进程打开哪些文件(文件就是存储在硬盘上的数据)
进程调度:
并行执行:每个CPU核心上,都可以独立运行一个进程
多个CPU核心上,就可以独立运行多个进程
并发执行:一个CPU核心上,先运行进程1,再运行进程2,再运行进程3....
宏观上,同时运行多个程序;微观上,同一时刻运行一个程序
4. 进程状态: R(可执行状态) S(可中断睡眠状态) X(退出状态) D (不可中断的睡眠状态)
T(暂停状态) Z(僵尸状态) [Linux进程状态解析之 R、S、D、T、Z、X (主要有三个状态)]
阻塞状态的进程: 无法被调度到CPU上执行
就绪状态的进程: 才可以在CPU上执行
5. 进程优先级:优先给谁安排谁先执行
系统调度的时候,就会根据优先级,来给进程安排执行时间
创建进程时,也可以通过系统调用来干预优先级
6. 进程上下文: 主要存储调度出CPU之前,寄存器的信息(把寄存器的信息保存到内存中),等到这个进程下次恢复到CPU上执行时,将内存数据恢复到寄存器中
通俗的说 就是 “读档” 与 “存档”
进程在CPU上执行,要切换到别的进程,就需要保存当前运行结果(存档),下次再到他执行时,就恢复之前的中间结果(读档),然后继续执行
7. 进程的记账信息:记录进程在CPU上的执行时长
通过记账信息来决定,进程在CPU上继续执行,还是调度出去
为了不出现进程间的相互影响,指针越界操作等情况,就需要让每一个进程都有各自的内存空间,不让这些进程的活动范围重叠,给每个进程都划分内存空间,叫做“虚拟地址空间”(不是真正的物理内存地址),通过专门的设备MMU来完成虚拟地址,到物理之前的映射
使用虚拟地址空间,就让进程存在了“隔离性”
(一个进程不能直接访问另一个进程的内存数据,防止干扰操作,提高系统的稳定性 )
但这样就会导致进程之间很难进行交互,为了解决这个问题就有了“进程间通信”
进程间通信 : 核心原理就是,找多个进程都可以访问到的公共资源,然后基于公共资源进行数据交换。
主流操作系统提供的进程通信机制有:文件、Socket(网卡)、管道、消息队列、磁盘、信号量、信号
一个线程就是一个执行流(按照一定顺序执行的一组代码),
每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行多份代码
同一个进程中的这些线程,共用一份系统资源(内存+文件)
进程是 资源分配的 基本单位
线程是 调度执行的 基本单位
首先是,因为“并发编程“可以充分利用CPU
其次,多进程虽然也可以实现并发编程,但线程比进程更轻量
(创建、销毁、调度线程都比进程更快)
所以,使用多线程:
1.能够充分利用多核CPU,能够提高效率
2.只是创建第一个线程时,需要申请资源,后续再创建新的进程,都是公用一份资源
销毁线程时,也是销毁到最后一个的时候,才真正释放资源,前面的线程销毁,都不必真正的释放资源
最后又有了更好的办法 "线程池"和"协程"
1. 进程包含线程(多个)
2. 线程比进程更轻量(创建、销毁、调度更快)
3. 同一个进程的多个线程之间共用一份内存/文件资源
进程和进程之间,则是独立的内存/文件资源
4. 进程是操作系统资源分配的基本单位
线程是操作系统调度执行的基本单位
1. 多线程 一定程度下,线程数目越多,效率越高;但CPU核心数是有限的,当线程数目多到一定程度的时候,CPU核心就被占满了,线程调度开销太大,反而效率变慢
2. 当两个线程去争夺同一资源时,此时两个线程就出现了问题,这就会使:”线程不安全“
3. 如果某个线程出现异常,并且异常没有处理好,此时就可能出现整个进程崩溃,此时其他线程也会运行出现问题
继承Thread类,重写run方法
- class MyThread extends Thread {
- @Override
- public void run() {
- System.out.println("Thread");
- }
- }
标准库中,提供一个Thread类,使用的时候就可以继承这个类
Thread类,相当于是对操作系统中的线程进行的封装
重写run方法,run是Thread父类中已有的方法,但是这里要重写
run中的逻辑就是这个线程要执行的工作
创建子类,重写run方法,相当于”安排任务“
- public class Demo {
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- myThread.start();
- }
- }
创建MyThread实例,并不会在系统中真的创建一个线程
而是,调用start方法的时候,才会真正的创建线程
新的线程才会执行run方法中的逻辑,直到run中的逻辑执行完,新的线程才会运行结束
- class MyThread01 extends Thread {
- @Override
- public void run() {
- while (true) {
- System.out.println("Hello Thread!");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class Test {
- public static void main(String[] args) throws InterruptedException {
- MyThread01 myThread = new MyThread01();
- myThread.start();
- while (true){
- System.out.println("hello main");
- Thread.sleep(1000);
- }
- }
- }
抢占式执行
第一步:找到Jdk安装路径在bin下找到——>jconsole.exe打开
第二步:启动测试类,在本地进程中选择当前测试项目,连接后就可查看项目中的线程
第三步:选择查看线程信息
创建Runnable实例,将Runnable实例作为参数传给线程
将线程要干的工作 和线程本身 分开了,使用Runnable来专门表示”线程要完成的工作“
这种方法的好处是, 降低了耦合度,如果以后要修改代码,改动的相对较少
只需要将修改后的Runnable传给其他的实体就可以了
并且如果是多个线程干一样的工作,Runnbale也更适合
- //创建一个Runnable的实现类,并实现Run方法
- //Runnable主要描述的是线程的任务
- class MyRunnable01 implements Runnable {
- @Override
- public void run() {
-
- System.out.println("hello thread");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 通过继承Runnable接口并实现run方法
- */
-
- public class Thread_demo02 {
- public static void main(String[] args) throws InterruptedException {
- //实例化Runnable对象
- MyRunnable01 runnable01 = new MyRunnable01();
- //实例化线程对象并绑定线程
- Thread thread = new Thread(runnable01);
- //真正的去申请系统线程参数CPU调度
- thread.start();
- while (true) {
- System.out.println("hello main");
- Thread.sleep(1000);
- }
- }
- }
创建Thread的子类,同时实例化出一个对象
- public static void main(String[] args) {
- Thread thread = new Thread() {
- @Override
- public void run() {
- while (true) {
- System.out.println("hello Thread");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- };
- thread.start();
- }
匿名内部类的实例作为构造方法的参数
- public static void main(String[] args) {
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello Thread");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- t.start();
- }
- public static void main(String[] args) {
- Thread thread = new Thread(()->{
- while (true){
- System.out.println("hello Thread");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- thread.start();
- }
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
使用构造方法给线程命名
- //使用构造方法给线程命名
-
- public class Demo2 {
- public static void main(String[] args) {
- Thread thread = new Thread(()->{
- while (true) {
- System.out.println("hello aka yty");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- },"线程名字:小杨要坚强");
- thread.start();
- }
- }
使用Jconsole.exe 查看线程名称:
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
(1)getId() 这个是Java中给Thread对象安排的身份标识, 和操作系统内核中PCB的pid、以及操作系统提供的线程API的线程id都不是一个
(2)isDaemon() 判断是否是后台(守护)线程。默认创建的线程是”前台线程“,前台线程会阻止进程退出,如果main运行完,前台线程还没完,进程不会退出;如果是”后台线程“,后台线程不会阻止进程退出,如果main执行完其他的前台进程执行完,此时即使后台线程没执行完,也会退出。
”只要有一个前台(main也是一个前台线程)线程,进程就还活着,前台没了,后台也就没了“。
设置操作得在sttart之前,如果线程启动,就不能修改
- public class isDaemon {
- public static void main(String[] args) {
- Thread thread = new Thread(()->{
- while (true){
- System.out.println("小杨");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- //使用setDaemon设置为后台线程
- //设置操作在start之前
- thread.setDaemon(true);
- thread.start();
- System.out.println("main线程执行结束!");
- }
- }
(3)isAlive()判断内核中线程是否存活。Thread对象,虽然和内核中的线程,具有一一对应关系,但是生命周期并非完全相同;Thread对象存在了,内核里的线程不一定有,调用start方法,内核线程执行完(run运行完),内核的线程就销毁了,但是Thread对象还在。
(4) Thread属性
- public class ThreadDemo {
- public static void main(String[] args) {
- Thread thread = new Thread(()->{
- while (true){
- System.out.println("小杨");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- thread.start();
- System.out.println(thread.getId());//ID
- System.out.println(thread.getName());//名称
- System.out.println(thread.getPriority());//优先级
- System.out.println(thread.getState());//状态
- System.out.println(thread.isDaemon());//是否为后台线程
- System.out.println(thread.isAlive());//是否存活
- System.out.println(thread.isInterrupted());//是否被中断
- }
- }
调用start才会真正创建线程,不调用start就没有创建线程(在内核里创建PCB)
start和run的区别(*)
直接调用run,并没有创建线程,只是在原来的线程中运行代码
调用start,则是创建了线程,在先线程中执行代码
作用和功能不同:
1. run方法的作用是描述线程具体执行的任务
2. start方法的作用是真正的去申请系统线程
运行结果不同:
1. run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
2. start调用方法后,start方法内部会调用Java本地方法(封装了对系统底层的调用)真正的启动线程,并且执行run方法中的代码,run方法执行完成后线程进入销毁阶段。
线程中断,本质就是让run方法尽快结束,而不是run执行一般,强制结束。
方法一:
直接自己定义标志位,作为线程结束的标志
- public class isInterrupted {
- private static boolean isQuit = false;
-
- public static void main(String[] args) {
- Thread thread = new Thread(() -> {
- while (!isQuit) {
- System.out.println("小杨");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("thread线程执行完了");
- });
- thread.start();
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- isQuit = true;
- System.out.println("设置标志让thread结束");
- }
- }
方法二:
使用标准库中自带的标记位
- public class isInterrupted {
-
- public static void main(String[] args) {
- Thread thread = new Thread(() -> {
- while (!Thread.currentThread().isInterrupted()){
- System.out.println("小杨");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println("thread线程执行完了");
- });
- thread.start();
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- thread.interrupt();
- System.out.println("设置让thread结束");
-
- }
- }
运行程序,出现异常,是因为interrupt方法的行为有两种情况
1. thread线程在运行状态会设置,标志位为true
2. thread线程在阻塞状态sleep 不会设置标志位,而触发一个InterruptedException
这个异常会把sleep提前唤醒。
所以 想要顺利结束循环 由线程代码自身进行判定处理
线程自身可以:
(1) 立即结束线程(break)
(2)啥也不干
(3)稍后处理(sleep,break)
线程之间的调度顺序是不确定的,可以通过join来控制线程之间的结束顺序
方法 | 说明 |
join() | 等待线程结束 |
join(long millis) | 等待线程结束,最多等millions毫秒 |
join(long millis,int nanos) | 也可以等待给更高精度 毫秒+纳秒 |
在main中调用join效果就是:让main线程阻塞等待,等到thread执行完,main才继续执行
- public class join {
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() ->{
- for (int i = 0; i < 5; i++) {
- System.out.println("hello thread");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- thread.start();
- System.out.println("main线程join之前");
- thread.join();
- System.out.println("main线程join之后");
- }
- }
currentThread(); 获取当前这个线程对于的Thread对象的引用
NEW: 安排了工作 , 还未开始行动(Thread对象创建出来了,但是内核的PCB还没创建,(还没真正创建线程))
RUNNABLE: 可工作的 . 又可以分成正在工作中和即将开始工作(就绪状态(正在CPU上运行+在就绪队列中排队))
BLOCKED: 这几个都表示排队等着其他事情(等待锁的时候进入阻塞状态)
WAITING: 这几个都表示排队等着其他事情(特殊的阻塞状态,调用wait)
TIMED_WAITING: 这几个都表示排队等着其他事情(按照一定的时间,进行阻塞sleep)
TERMINATED: 工作完成了 (内核的PCB销毁了,但是Thread对象还在)
- public class getState {
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(() -> {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
-
- //thread开始之前 得到的是 NEW
- System.out.println(thread.getState());
- thread.start();
-
- Thread.sleep(50);
- //thread正在工作,得到的是 RUNNABLE
- System.out.println(thread.getState());
-
- thread.join();
- //thread结束之后,得到的状态是 TERMINATED
- System.out.println(thread.getState());
- }
- }