首页 > 系统相关 >Java并发编程实战 01 | 进程和线程

Java并发编程实战 01 | 进程和线程

时间:2024-09-05 16:52:39浏览次数:6  
标签:01 Java 操作系统 线程 切换 进程 执行 CPU

最早的计算机就像一个新手服务员,只有在接收到每一条指令时才会开始执行。当用户输入指令时,计算机会执行这条指令,然后等待下一条指令。如果用户在思考或者犹豫时,计算机就会乖乖地等待,虽然这很规范,但效率实在是有点低,因为计算机有很多时间是闲着的。

批处理操作系统

后来,批处理操作系统出现了,它就像一个升级版的服务员,能够一次性接收一系列指令,并按顺序执行。这种系统允许用户将要执行的程序写入一盘磁带中,然后让计算机读取这些指令并执行,同时将结果写入另一盘磁带上。

虽然批处理操作系统在一定程度上提高了计算机的运行效率,但由于它仍然是串行处理的,内存中一次只能运行一个程序。后续的程序必须等到前一个程序完全执行完毕后才能开始执行。如果前一个程序因为I/O操作、网络请求等原因被阻塞,整个批处理的运行效率就会受到影响。因此,批处理操作系统的效率依然有限。

进程

随着科技的发展,人们对计算机性能的要求越来越高,现有的批处理操作系统已无法满足这些需求。批处理操作系统的瓶颈在于一次只能运行一个程序。

为了解决批处理操作系统的瓶颈,科学家们提出了一个新的概念——进程。

进程就是正在运行的程序。比如,当你启动一个 Java 程序时,实际上是启动了一个 Java 虚拟机进程。换句话说,一个正在运行的 Java 程序就是一个 Java 虚拟机进程。这个概念使得计算机能够同时运行多个程序,从而大大提高了计算机的效率和性能。

操作系统可以同时运行多个进程,比如 Chrome 浏览器、Facebook 等,它们之间互不干扰。每个进程保存了程序每时每刻的运行状态,使得系统能够高效地管理和切换这些进程。

为实现这一点,CPU 使用时间片轮转调度算法来运行进程。这个算法的基本原理是:CPU 为每个进程分配一个固定的时间段,称为时间片。在时间片结束时,如果进程仍在运行,它会被暂停,然后 CPU 会分配给另一个进程,这个过程称为上下文切换。如果在时间片结束前,进程被阻塞或完成,CPU 会立即进行切换,无需等待时间片用完。

当一个进程被暂停时,操作系统会保存该进程的当前状态(进程控制块PCB)。下次切换回来时,操作系统会根据之前保存的状态恢复进程,继续执行。

虽然从宏观上看,操作系统似乎能够在同一时间段内执行多个任务,实际上,对于单核 CPU 来说,在任意时刻,只会有一个进程占用着 CPU 资源,这称之为并发,与并发这个词还有一个相似的概念叫做并行。

并发和并行

在计算机中,并发意味着多个任务在同一时间段内看似同时执行,但实际上它们是通过快速切换来共享 CPU 时间的。虽然每个任务并不是真正同时进行的,但如果切换得够快,用户看起来就像是多个任务同时在运行。

并发就像一个人(单核 CPU)在同时处理多件事情。想象你是一个服务员,负责照顾几张桌子上的客人。你可以走到第一桌,记下点的饮料,然后走到第二桌,记下点的主菜,再走到第三桌,记下甜点。虽然你在同一时刻只能服务一桌客人,但通过快速地在几桌之间切换,看起来好像你在同时为每桌提供服务。这就是并发。

并行意味着多个任务在不同的处理器(或 CPU 核心)上同时执行。因为每个任务都有自己独立的计算资源,所以它们是真正意义上地同时进行。

并行就像有多个服务员(多核 CPU)同时在不同的桌子上服务客人。每个服务员负责一张或几张桌子,他们可以真正同时接单、上菜、收盘子。这样,每张桌子都能独立得到服务,而不需要等待其他桌子的服务结束。这就是并行。

线程

尽管进程的引入显著提升了操作系统的性能,但随着时间的推移,人们希望单个进程能够处理更多的任务。如果一个进程中的多个子任务只能逐一执行,还是有很大的缺陷。

举个例子,当你使用浏览器浏览网页时,看到一个感兴趣的文件想要下载,如果在下载文件的过程中,浏览器无法继续加载其他网页,这显然是一个不好的用户体验。

为了解决这个问题,人们又引入了线程的概念。线程是进程中的一个执行单元,每个线程可以独立地完成一个子任务。这样,一个进程可以包含多个线程,如果需要,多个线程可以并发地执行。

例如,下面的 Java 代码展示了如何在主线程中启动两个新线程,每个线程负责执行不同的任务,一个线程负责打印 “hello world”,另一个线程负责打印 “I love you”:

class SayHelloThread extends Thread {
    public void run() {
        System.out.println("Hello world");
    }
}

class SayLoveThread extends Thread {
    public void run() {
        System.out.println("I love you");
    }
}

public class MultiThreadJavaApp {

    public static void main(String[] args) throws InterruptedException {
        SayHelloThread sayHelloThread = new SayHelloThread();
        SayLoveThread sayLoveThread = new SayLoveThread();
        sayHelloThread.start();
        sayLoveThread.start();
        //main thread sleep
        Thread.sleep(5000);
    }
}

//输出:
hello world
I love you

有趣的是,上面的程序每次运行时,输出的结果可能并不相同。这是因为 sayHelloThread 不一定总是能先获得执行的机会,具体哪个线程先执行,取决于操作系统的调度算法。

引入线程后,任务管理变得更加灵活和高效。当你在浏览器中下载文件时,可以由一个专门负责下载的线程来处理这个任务,而用户继续浏览网页时,浏览器则会运行另一个负责页面加载的线程。通过时间片轮转调度,操作系统可以在这些线程之间快速切换,从而让用户感觉下载和浏览网页的操作是在同时进行的。

进程和线程之间的区别

进程和线程的引入极大地提升了操作系统的性能,但它们之间到底有什么区别呢?

1. 资源占用

进程是操作系统分配资源的基本单位,包含程序执行的一个实例,包括代码、数据和系统资源(例如内存、文件、设备等)。每个进程都有独立的内存空间和系统资源,互不干扰。

线程是CPU调度的基本单位,多个线程共享同一个进程的内存空间和系统资源,但是每个线程拥有独立的栈、寄存器和程序计数器

2. 数据交换

进程之间是相互独立的,每个进程有自己的地址空间和系统资源。因此,进程之间的数据交换必须通过进程间通信(IPC)机制来实现,例如管道、消息队列、共享内存等,这种方式比较复杂。

线程是同一进程内的不同执行路径,共享同一个进程的内存空间和系统资源,因此线程之间的数据交换更加简单和快捷。

3. 开销

进程由于有独立的内存空间和系统资源,创建和销毁进程需要较大的开销,包括分配内存、加载程序、保存和恢复上下文信息等操作。

线程共享进程的内存空间和系统资源,创建和销毁线程的开销要小得多,仅需要保存和恢复少量的上下文信息,因此线程的切换成本较低。

4. 并发性

进程是独立的执行单元,拥有各自的调度算法,在并发执行时更加稳定可靠,因为一个进程的问题通常不会直接影响其他进程。

线程由于共享同一进程的资源,调度和同步相对复杂,需要仔细处理共享数据的并发访问,避免出现数据不一致或竞争条件等问题。这也是我们在后续文章中会深入讨论的一个关键点。

基于上面的区别,我们可以看到,在一个进程内实现多个任务的并发,最合适的方法是使用多线程,而不是多进程。不过,需要特别注意处理好并发逻辑,防止线程间的数据竞争和同步问题。

然而,这并不意味着多线程一定比多进程更优。在 Linux 系统中,创建进程的开销相对较小,因此 Linux 系统鼓励更多地使用多进程。但是,多进程的一个常见问题是进程间通信比较复杂和不方便,因此在 Linux 中,学习的重点之一就是掌握各种进程间通信(IPC)的方法。

简单来说,进程的引入使得操作系统可以实现多个程序的并发运行,而线程的引入则使得一个进程内部可以并发执行多个任务。

上下文切换

上下文切换(有时称为进程切换或任务切换)是指 CPU 从一个进程(或线程)切换到另一个进程(或线程)的过程。这里的“上下文”指的是 CPU 在某个时间点的所有状态信息,包括寄存器和程序计数器的内容。

寄存器是 CPU 内部少量的高速存储器,用于保存和访问运算过程中的中间值,从而提高计算速度。

程序计数器是一个专用的寄存器,用来指示 CPU 当前正在执行的指令(或将要执行的下一条指令)在指令序列中的位置。它存储的值可以是正在执行的指令的位置,也可以是下一条即将执行的指令位置,这取决于具体的系统实现。

CPU 通过为每个线程分配一定的时间片来实现多线程机制,并使用时间片轮转调度算法来执行任务。当一个线程执行完一个时间片后,CPU 就会切换到下一个线程。在切换前,CPU 会保存当前线程的状态信息,以便在将来切换回这个线程时能够恢复到之前的状态。

这个过程,也就是从保存当前任务状态到重新加载另一个任务状态的过程,就称为上下文切换。

假设当前线程 A 的时间片用完,需要切换到线程 B,操作系统会进行以下步骤:

  1. 暂停线程 A,将其在 CPU 中的状态(如寄存器、程序计数器等)保存到内存中。
  2. 从内存中加载线程 B 的上下文信息,并将这些信息恢复到 CPU 的寄存器中,以便执行线程 B。
  3. CPU根据程序计数器(此时保存的是将要执行的线程B的指令位置)的指示执行线程B的代码。

上下文切换通常是一个计算密集型操作,需要消耗大量的 CPU 时间。因此,线程数量增加并不总是能提高性能。如何减少系统中的上下文切换次数,是提升多线程性能的一个关键问题,我们将在后续的文章中进一步探讨。

标签:01,Java,操作系统,线程,切换,进程,执行,CPU
From: https://blog.csdn.net/weixin_42627385/article/details/141935181

相关文章

  • Java并发编程实战 02 | 为什么创建线程只有一种方法?
    在Java中,我们如何创建和使用线程?为什么说线程的创建方式本质上只有一种呢?本文将从并发编程的基础——如何创建线程开始,希望大家能够打好基础。虽然线程的创建看起来很简单,但其中还是有很多细节值得深入探讨。最后,我们将揭开线程实现的面纱,看清它的本质。首先,大家可以思考......
  • Java环境配置包含Maven,idea配置,保姆级教程!
    1.本期工具Maven:https://maven.apache.org/Java:https://www.oracle.com/cn/java/technologies/downloads/#java22Idea:https://www.jetbrains.com/zh-cn/idea/download/?section=windows2.Java安装配置1.jdk下载官网下载:https://www.oracle.com/cn/java/techn......
  • 基于JAVA的个人理财系统设计与实现的计算机毕设
    基于JAVA的个人理财系统设计与实现摘要随着现在社会的快速发展和进步,人们的生活水平也不断提高,人们不仅在生活物质上提高,而且在整体素质上也不断提高,在某个职业道德也投入了很多的资金和时间。对于一些年轻的人来说,在资金和物质上有时候不知道该如何去操作,也更不知道去如何将这......
  • Java中的图像复原:如何实现高效的去雾与去雨算法
    Java中的图像复原:如何实现高效的去雾与去雨算法大家好,我是阿可,微赚淘客系统及省赚客APP创始人!今天我们来探讨在Java中如何实现高效的图像复原技术,特别是去雾和去雨算法的实现和优化。一、图像复原的概念与挑战图像复原是指在受到各种干扰的图像中恢复出原始的清晰图像。......
  • Java毕设项目II基于Java的英语知识应用网站
    目录一、前言二、技术介绍三、系统实现四、论文参考五、核心代码六、源码获取全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末一、前言在全球化日益加深的今天,英语作为国际交流......
  • java+vue计算机毕设社区独居老人健康管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着我国人口老龄化的加速,独居老人的数量显著增加,这一群体在健康管理上面临着诸多挑战。传统的养老模式难以全面覆盖并有效满足独居老人的健康需求,特......
  • java+vue计算机毕设汽车租赁管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速和居民生活水平的提高,汽车租赁作为一种便捷、灵活的出行方式,日益受到广大消费者的青睐。传统汽车租赁行业面临着管理效率低下、......
  • java+vue计算机毕设求职招聘管理系统【源码+开题+论文】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着互联网的飞速发展,网络招聘已成为企业与求职者之间沟通的主要桥梁。传统的招聘方式,如招聘会、报纸广告等,不仅成本高、效率低,而且难以精准匹配企业......
  • 【JavaScript学习第六天】—讲述JS学习历程的知识分享!
    前言本篇主要讲述了面向对象开发的特点,对象和类的概念与区别,包括详细讲解一个Tab选项卡案例一、面向对象在引出面向对象之前,我们首先要了解面向过程的概念面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了......
  • web-worker 独立线程,性能优化
    ref:https://github.com/zjy4fun/web-worker分别使用主线程和worker线程处理一个耗时计算,看看对主线程上的UI渲染有什么影响 <!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content=&q......