进程与线程的初探

什么是进程?

        现代的操作系统需要运行各种各样的程序。为了管理这些程序的运行,操作系统提出了进程(process)的抽象:每一个进程对应一个运行中的程序。

进程

进程的状态

        为了对进程进行管理,操作系统定义了进程的状态。

  • 新生状态:进程刚刚被创建处理,还未完成初始化。
  • 就绪状态:进程可以被调度执行,但是还没有被调度器(管理进程的执行能够切换进程的状态)选择。
  • 运行状态:进程正在cpu运行。
  • 阻塞状态:该进程需要等待外部事件的完成(例如I/O请求)
  • 终止状态:进程完成执行,且不会在被调度。
进程在不同状态之间的切换

进程的内存空间

  • 用户栈:保存了进程需要使用的各种临时的数据。栈是可以伸缩的数据结构,扩展方向自顶向下,栈底在高地址,栈顶在低地址。临时数据被压入栈时,栈顶向低地址扩展。
  • 用户堆:管理进程动态分配的内存,扩展方向自底向上,堆顶在高地址,堆底在低地址。进程需要更多的内存,堆顶向高地址扩展。
  • 代码库:进程执行时有时需要依赖共享代码库。
  • 数据与代码段:数据段保存的是全局变量的值,代码段保存的是进程需要执行的代码。
  • 内核部分:进程在用户态运行时,内核内存对其不可见,当进程进入内核态,才能访问内核内存。
内存空间布局

进程控制块和上下文切换

      在内核中,每个进程通过一个数据结构保存它的状态,包括进程标识符(PID),进程状态,虚拟内存状态,打开的文件等。这个数据结构称为进程控制块(PCB)。

        进程的上下文包括进程运行时寄存器的状态,它能够保存和恢复一个进程在处理器上运行的状态。当操作系统需要切换当前执行的线程的时候,就会使用上下文切换机制。该机制会使前一个进程的寄存器状态保存到PCB中,然后将下一个进程先保存的状态写入寄存器中,切换线程。
进程之间的切换必须先切换到内核态,就是说系统调用相关的数据信息必须存储在内核空间中,然后执行系统调用。

        如下图:当进程1由于中断或者系统调用进入内核态,先把上下文保存对应的PCB中,如果进行线程的切换,恢复对应线程的PCB上下文。

线程之间的切换

并发和并行:

并发是指一个处理器同时处理多个任务,而并行是指多个处理器或多核的处理器同时处理多个不同的任务 

 线程

        在早期的操作系统中,进程是操作系统管理运行程序的最小单位。但是由于一些原因。

  1. 创建进程的开销比较大,需要完成创建独立的地址空间,载入数据和初始化等步骤。
  2. 进程之间的数据同步比较麻烦

因此,操作系统的设计者在进程内部添加了可独立执行的单元,它们之间共享内存地址空间,但是有各自保存上下文,这就是线程。之后,线程变成操作系统调度和管理程序的最小单位。

线程的优点:

  1. 一个进程中可存在多个线程
  2. 各个线程之间可以并发执行
  3. 各个线程之间可以共享内存地址空间和文件等资源

进程和线程的比较

  • 进程是资源的分配单位,线程是CPU调度的单位。
  • 线程同样有就绪,阻塞,执行三中基本状态,也具有它们之间的转换关系。
  • 进程是一个完整的资源平台,线程只独享必不可少的资源
  • 线程能够减少并发执行的时间和空间开销
  1. 线程创建比进程块,因为进程创建过程中需要管理资源信息,例如:内存管理,文件管理,但是线程不会涉及到管理这些信息,线程之间共享这些信息。
  2. 同一个进程之间的线程切换比进程之间快,线程之间具有相同的地址空间,但是进程之间没有相同的地址空间。
  3. 由于同一个进程的线程之间共享内存和文件资源,那么在线程之间传递数据,不需要内核,实现从之间的交互效率变高
  4. 线程的终止时间比进程快,因为线程释放的资源较少

线程创建的代码实现

1.继承Thread,重新run方法

package Thread;
// 线程的创建(1)
/**
 * 继承Thread类并重写run方法
 */
// 线程2:通过创建 MyThread 类的一个实例并调用其 start 方法,创建了一个新的线程
class MyThread extends Thread {
    // 标记为重写方法(Override),覆盖Thread类中的run方法
    @Override
    // 线程的入口方法,新的线程启动执行这里的代码,不需要手动调用,新的线程创建好后,自动的去执行
    // 相当于回调函数,写好的函数自己不去调用,交给别人来调用
    public void run() {
//        for(int i = 0;i < 5;i++) {
//            System.out.println("hello thread");
//        }
        while(true) {
            System.out.println("hello thread");
            try {
                // 当前线程暂停执行1000ms
                Thread.sleep(1000);
                // 抛出 InterruptedException ,因为线程在休眠期间可能会被其他线程调用interrupt()方法打断
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class demo1 {
    // 线程1:main,JVM启动程序时就创建了
    public static void main(String[] args) throws InterruptedException {
        // 创建MyThread类的实例,实例是一个线程对象
        // Thread是java.lang 包的一部分,Java编译器自动导入java.lang这个包
        // 向上转型,MyThread是Thread的一个子类,将子类对象的引用赋值给父类变量,可以用父类的引用变量处理不同的子类对象
        Thread t = new MyThread();
        // start创建,真正在系统中创建线程(JVM 调用操作系统的API完成线程的创建操作)
        t.start();
        // t.run()这个操作没有创建线程,只是调用了重写的run,进程中只有一个main线程,这样子还是一个单线程
        // t.run();
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

1.sleep是静态方法,是让当前的线程暂时放弃cpu,过一段时间在执行
2.打印出的结果并不是按照“hello main”和“hello thread”顺序执行的,而是两者进行抢占式执行,即多个线程的调度顺序是随机的,线程执行的先后顺序是不确定的

2.实现Runnable,重写run方法

class MyRunnable implements Runnable{
    @Override
    public void run() {
        while(true) {
            System.out.println("hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        // MyRunnable类实现了Runnable接口,MyRunnable的实例可以被赋值给Runnable类型的引用变量runnable。向上转型
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

高内聚:一个模块之间,有关联的东西放一起
低耦合:模块之间的依赖关系较少

实现Runnable接口并重写run方法与继承Thread类并重写run方法相比:

  • 避免单继承的限制: Java不支持多继承.通过实现Runable接口,可以在继承其他类的同时实现多线程,从而避免了因为继承Thread类而失去继承其他类的能力。

  • 更好的解耦: 实现Runnbale接口可以使得类与线程的实现解耦和。提高了代码的可维护性和可扩展性。

  • 资源共享:实现一个接口,创建多个实例,将这些实例传给不同的对象,由于这些线程调用相同的方法,因此可以共享相同的资源

  • 降低程序复杂度: 更清楚地表明类是为了执行任务而设计的,而不是表示线程本身。

3.其他的创建方式

方式1的匿名内部类以及方式2的匿名内部类和lambda表达式

4.Thread类的其他的属性和方法

方法说明
Thread()必须重新Thread的run方法创建线程对象
Thread(Runnable target)Runnable对象创建线程对象
Thread(String name)创建线程对象并命名
Thread(Runnable target,String name)使用Runnable创建线程对象并命名
Thread(ThreadGroup group,Runnable target)线程可以被分组管理,分好的就是线程组

5.前台线程和后台线程

前台线程(非守护线程):程序的主要执行线程,通常执行重要的任务,不依赖其他线程来执行

特点:

  • 通过继承Thread类或实现Runnable接口创建的线程默认是前台线程
  • 前台线程不显式的结束,程序不会终止,即使前台线程完成了工作,如果有前台线程正在运行,程序也不会终止

后台线程(守护线程):为前台线程提供服务和支持

特点:

  • 所有前台线程结束,即使后台线程仍在运行,jvm也会退出
  • 可以通过setDamon(True)的方式将线程设置为后台线程
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            while(true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        /* 将t设置为守护线程,当main线程结束后,守护线程必须结束
         * 由于t是一个守护线程,当主线程的for循环结束后,
         * 主线程会打印"main结束",然后主线程结束。因为此时JVM中只剩下守护线程 t,
         * 所以JVM将会退出,守护线程 t也将被终止。
         */

        t.setDaemon(true);
        t.start();
        /* 如果守护线程在主线程结束前有足够的时间运行,它可能会打印四次或更多次"hello thread"。
        这完全取决于线程调度器的行为和系统的当前状态。*/
        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
        System.out.println("main结束");
    }

线程中的一些方法

1.isAlive()

Thread对象的生命周期:
Thread对象的生命周期是指Thread对象的整个生命周期,从创建到垃圾回收。Thread对象的生命周期可以独立于线程的生命周期存在,即使线程已经终止,Thread对象仍然可能存在,直到被垃圾回收器回收。

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello thread");
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        // 没有调用start,结果是false
        System.out.println(t.isAlive());
        t.start();
        while(true) {
            System.out.println(t.isAlive());
            Thread.sleep(1000);
        }
        // false true true true false false... 线程结束但是Thread的生命周期还没有结束
    }

2.start()

对于同一个线程对象,start()只能调用一次,如果多次调用,抛出IllegalThreadStateException异常,调用start()线程由新建变为可运行状态

3.线程终止interrupt()

此处是让线程直接停止,不会再恢复

class Test{
    public int val = 0;
}

public class demo10 {
    /*isFinished不可以定为局部变量,isFinished应该是final
    涉及lambda表达式的变量捕获,lambda表达式是回调函数
    执行时机是很久以后,有可能后续线程创建好了,main方法执行完成,对应的isFinished销毁
    为了解决这个问题,Java是把捕获的变量拷贝到lambda表达式中,这就意味着这样的变量不适合修改
    因此规定不允许修改*/
    /*但是如果变成了成员变量 此时不再是变量捕获的语法,而是切换成内部类访问外部类成员的语法
    成员变量的生命周期,也是让GC来管理的,在lambda表达式里面不担心变量生命周期失效的问题
    也就不必拷贝,也就不必限制final之类的
    */
    private static boolean isFinished = false;

    public static void main(String[] args) throws InterruptedException {
//      boolean isFinished = false;
       Test test = new Test();

        Thread t = new Thread(()->{
            while(!isFinished) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread 结束");
            System.out.println(test.val);
            test.val++;     // 修改对象本体可行
//            Test test = new Test(); 修改对象引用不可行
        });
        t.start();
        Thread.sleep(3000);
        isFinished = true;
    }
}

isInterrupted()是Thread的一个实例方法,为了判断当前线程释放被中断,返回一个布尔值,注意,在lambda表达式中不可以直接t.isInterrupted(),因为lamada定义在new Thread之前,也是在Thread t之前,不能够t.isInterrupted(),那么如何解决,利用Thread.currentThread() 是一个静态方法,哪一个线程调用,获取的就是哪个线程的引用,这里是t。

那么就是Thread.currentThread().isInterrupted()用来判断线程是否终止,调用interrupt()主动终止线程

4.等待线程join()

join能够要求多个线程之间的结束先后顺序,例如主线程调用t.join(),表名是主线程等待t线程的结束。当执行到t.join()时,主线程就会“阻塞等待”,一直等到t线程执行完毕,主线程才能执行。如果里面没有传参,那么就是“死等”,主线程的join就一直等下去,直到线程终止;join()里面传参,毫秒为单位,如果这段时间内线程终止,则立即返回,如果大于等待时间,t还没有结束,那么就不会继续等待

线程状态

  1. NEW:线程已经创建但尚未启动。即线程对象已经被实例化,但是start()方法还没有被调用。

  2. RUNNABLE:线程正在Java虚拟机中执行,但它可能正在等待操作系统的其他资源,如处理器。线程调度器可以随时选择RUNNABLE状态的线程来执行。

  3. BLOCKED:线程正在等待监视器锁(monitor lock)来进入一个同步块/方法,或者在调用Object.wait()后重新进入同步块/方法。

  4. WAITING:线程正在等待另一个线程执行特定操作。死等。

  5. TIMED_WAITING:线程正在等待另一个线程执行特定操作,但有一定的超时时间。

  6. TERMINATED:线程已经完成了执行,内核中的线程结束,但是Thread对象还在

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DNS服务器部署

一、正反向分析 二、主从服务器配置 前提操作&#xff1a; 虚拟机关闭防火墙 一、正反向分析 1、主配置文件 2、正向分析 3、反向分析 二、主从服务器配置 从服务器主配置 从服务器Type改为slave。 编写正反向分析文件。 测试&#xff1…

Spring DispatcherServlet详解

文章目录 Spring DispatcherServlet详解一、引言二、DispatcherServlet的初始化与工作流程1、DispatcherServlet的初始化1.1、加载配置和建立WebApplicationContext1.2、初始化策略 2、DispatcherServlet的工作流程2.1、请求分发2.2、代码示例 三、总结 Spring DispatcherServl…

计算机性能分析的三个模型

计算机性能分析的三个模型【1】 一、瓶颈分析&#xff08;Bottleneck Analysis&#xff09;二、利特尔法则&#xff08;Littles Law&#xff09;【2】三、M/M/1 QueueReference 一、瓶颈分析&#xff08;Bottleneck Analysis&#xff09; 瓶颈分析可以帮我们更好地定位导致性能…

Golang | Leetcode Golang题解之第520题检测大写字母

题目&#xff1a; 题解&#xff1a; func detectCapitalUse(word string) bool {// 若第 1 个字母为小写&#xff0c;则需额外判断第 2 个字母是否为小写if len(word) > 2 && unicode.IsLower(rune(word[0])) && unicode.IsUpper(rune(word[1])) {return f…

开发一个基于Delphi的题库生成系统

开发一个基于Delphi的题库生成系统 步骤一&#xff1a;需求分析 首先明确系统需要实现的功能&#xff0c;比如&#xff1a; 添加题目编辑题目删除题目题目分类管理随机生成试卷导出试卷为PDF或Word格式 步骤二&#xff1a;设计数据库 使用SQLite或其他轻量级数据库存储题…

红队-shodan搜索引擎篇

如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 一.shodan原理与功能的介绍 Shodan Search Engine 它是专门搜网络设备的,只要联网的,只要有IP地址的都可以称为网络设备 1.shodan&#x…

机器学习中的数据可视化:常用库、单变量图与多变量图绘制方法

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Python复习2

一、封装函数 #自己封装len函数 s1 "hello,python" print(f"s1的长度为{len(s1)}")def my_len(data):count0for i in data:count 1print(f"{data}的长度为{count}")my_len(s1) 二、容器的排序&#xff08;排序之后的结果都会变成列表&#xf…

从0开始深度学习(23)——图像卷积

上节了解了卷积层的原理&#xff0c;本节以图像为例&#xff0c;介绍一下它的实际应用 1 互相关运算 严格来说&#xff0c;卷积层是个错误的叫法&#xff0c;因为它所表达的运算其实是互相关运算&#xff08;cross-correlation&#xff09;。 首先&#xff0c;我们暂时忽略通…

代码随想录算法训练营第十二天| 226.翻转二叉树、101. 对称二叉树、104.二叉树的最大深度 、111.二叉树的最小深度

226.翻转二叉树 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;听说一位巨佬面Google被拒了&#xff0c;因为没写出翻转二叉树 | LeetCode&#xff1a;226.翻转二叉树_哔哩哔哩_bilibili《代码随想录》…

2024阿里云CTF Web writeup

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect 前言 又是周末…

【CSS】CSS 样式重置 (normalize.css 和 reset.css) 和通用样式配置

一般来说&#xff0c;每一个项目初始化阶段都需要样式重置和样式定制化。样式重置最常用的就是 normalize.css 和 reset.css 这两个文件。 他们的区别&#xff1a; Normalize.css更加注重保留有用的浏览器默认样式&#xff0c;仅修复浏览器之间的不一致性&#xff0c;适用于需…

动态规划——两个数组的dp问题

目录 一、最长公共子序列 二、不同的子序列 三、通配符匹配 四、正则表达式匹配 五、两个字符串的最小ASCII删除和 六、最长重复子数组 七、交错字符串 一、最长公共子序列 最长公共子序列 第一步&#xff1a;确定状态表示 dp[i][j]&#xff1a;表示字符串 s1 的 [0&am…

安卓13默认连接wifi热点 android13默认连接wifi

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 有时候我们需要让固件里面内置好,相关的wifi的ssid和密码,让固件起来就可以连接wifi,不用在手动操作。 2.问题分析 这个功能,使用普通的安卓代码就可以实现了。 3.代…

Kubernetes:(三)Kubeadm搭建K8s 1.20集群

文章目录 一、Kubeadm安装流程二、实验1.环境准备2.所有节点安装kubeadm&#xff0c;kubelet和kubectl&#xff08;除了Harbor节点&#xff09;3.部署 Dashboard4.安装Harbor私有仓库 一、Kubeadm安装流程 集群名称IP地址安装软件master&#xff08;2C/4G&#xff0c;cpu核心数…

杨传辉:云+AI 时代的一体化数据库|OceanBase发布会实录

在 2024 OceanBase 年度发布会 上&#xff0c; OceanBase CTO 杨传辉进行了主题为《云和 AI 时代的一体化数据库战略思考》的演讲&#xff0c;本文为演讲实录&#xff0c;欢迎阅读。 视频观看可点击&#xff1a;https://www.oceanbase.com/video/9001825 各位 OceanBase 的客…

ChatGPT变AI搜索引擎!以后还需要谷歌吗?

前言 在北京时间11月1日凌晨&#xff0c;正值ChatGPT两岁生日之际&#xff0c;OpenAI宣布推出最新的人工智能搜索体验&#xff01;具备实时网络功能&#xff01;与 Google 展开直接竞争。 ChatGPT搜索的推出标志着ChatGPT成功消除了即时信息这一最后的短板。 这项新功能可供 …

QT——记事本项目

目录 1.给pushButton按键添加图片 1.1 首先复制存放图片的文件夹&#xff0c;打开Qt回到编辑页面&#xff0c;右键单击pro文件选择在Explorer中显示&#xff0c;将图片文件夹粘贴进去你的代码同目录即可 1.2 创建一个新的文件夹 1.3 点击Add Files&#xff0c;将所有图片添加…

【在Linux世界中追寻伟大的One Piece】Socket编程TCP(续)

目录 1 -> V2 -Echo Server多进程版本 2 -> V3 -Echo Server多线程版本 3 -> V3-1 -多线程远程命令执行 4 -> V4 -Echo Server线程池版本 1 -> V2 -Echo Server多进程版本 通过每个请求&#xff0c;创建子进程的方式来支持多连接。 InetAddr.hpp #pragma…

为什么可视化大屏要有动态效果,都有哪些类型的效果。

可视化大屏已成为企业和组织展示关键信息的重要工具。这些大屏不仅需要清晰地传达数据&#xff0c;还要吸引观众的注意力并提供深刻的洞察。动态效果在这一过程中扮演着至关重要的角色。 动态效果的重要性 动态效果在可视化大屏中的应用&#xff0c;基于以下几个核心原因 吸…