前言
从本节开始,数据库阶段的学习算是结束了,我们将进入 JavaEE 初阶的学习。本阶段内容将会涉及到 Java 中较为底层的知识 ,学完后,不仅能了解到计算机的核心工作机制,还有利于理解我们平时编程时使用的一些行为 。更重要的是,JavaEE全部写完后,我们就可以写项目、做网站 了,项目对于我们之后的就业能提升很大的帮助,对于我们秋招求职可以说是必不可少的东西。
做网站、网页都会涉及到前端和后端。前端是由HTML + CSS + JS 构成的,HTML 负责网页的结构搭建以及定义文本、图片等元素 ;CSS 负责网页样式的美化,控制上述元素的布局、外观、大小的外部表现 ; JS 即 JavaScript ,负责网页的交互逻辑,实现弹窗、数据渲染等功能 。但是微信小程序的前端页面与网页前端不同 ,它不是标准的 HTML + CSS + JS,而是腾讯魔改过的。
1. 冯诺依曼体系结构
1.1 现代计算机的组成
一台现代计算机的组成,一般可由 CPU(即中央处理器)、主板、内存、硬盘、电源、散热器、显卡、键盘鼠标、显示器等等来共同组成的。现在我们来介绍主要的部件:
CPU 它是计算机中最核心的部分 ,它能进行各种算术运算、逻辑判断,是人类科技的巅峰之作。因为 CPU 可能表面上很小,但是其内部结构非常非常复杂,能达到量子力学的角度 。 CPU 的处理速度一秒能达到39亿次 ,是计算机中读写或处理速度最快的 。CPU 内还存在着几十个寄存器 ,但是这些寄存器的存储容量甚至远不如内存大。
内存和硬盘,它们的作用虽然都是用来存储或读取数据,但是在某些方面区别很大,例如:
-
在读写或处理速度上,内存速度快,硬盘速度慢。硬盘的读写速度一般是200MB/s,而内存则是硬盘的几千倍;
-
在存储空间上,内存的空间比较小,硬盘的空间比较大;
-
内存的成本比硬盘的成本更高;之前我的 1TB 硬盘还没涨价的时候,花了三四百;而 32G的内存,需要500元左右。所以差距还是挺大的;
4.**内存存储的数据不是持久的,断电后就没有了;而硬盘存储的数据是持久的,断电后也不会丢失。**但是如果一块硬盘放个几年不用,数据依然有可能会消失。
显卡,全称显示接口卡,主要作用是负责图形运算。显卡里包含着 CPU 和 GPU ;CPU 是 " 通用计算芯片 ",简单计算场景和困难计算场景它都能处理 ,但是能处理的数据量不算很大;而 GPU 是 " 专用计算芯片 ",它是专门用来处理运算虽然简单,但是运算量很大的场景。例如游戏。
1.2 冯诺依曼体系结构的组成

-
CPU 中央处理器:用来进行算术运算和逻辑运算;
-
存储器:分为外存和内存,用于存储数据(以二进制的方式);
-
输入设备:用户给计算机发送指令的设备;即数据给到 CPU 的过程就是输入。
-
输出设备:计算机将结果汇报给用户的设备。即数据从 CPU 给到用户的过程就是输出。
当然也有的设备既是输入设备,也是输出设备。例如触摸屏和网卡。网卡它是集成到主板上的,所以下载数据是输入,上传数据就是输出了。
2. 现代 CPU 的核心指标
现代的 CPU ,主要关心的指标和日常开发是密切相关的,主要有以下几种:
(1)CPU 的频率
CPU 的频率分为基频(或默频)和睿频(加速频率)。

打开任务管理器,点到性能那一栏,这里的基准速度就是基频,达到了2.40GHz;1G 就相当于10^9次方,即10亿。由于 Hz 是代表一秒XX次,所以我的电脑基频则是一秒钟能处理24亿次。睿频则是带有 "速度"的那一栏。
现代 CPU 的一个特性是,它能根据当前的任务量来动态调整频率。防止总是维持在高频而导致问题。
(2)CPU 和核心数
CPU 的内部构造是由数不清的小的电路元件组成的计算单元,计算单元越多,算的就越快 。在早期的 CPU 都是单个核心的。随着时代发展,单个核心的 CPU 就算不断提升频率也达到了瓶颈,所以人们将 CPU 单核提升到多核 。CPU 核心数越多,CPU 就越强,但是也对程序员提出新的要求:写代码的时候,需要把任务拆分成多个部分,交给多个核心去做;如果分配的不好,就两三个甚至只有一个核心在工作,那是不行的。
从单核提升到多核后,还分出了大核和小核。一个大核能顶两个小核所干的活,甚至还有全大核的处理器。但是功耗也随之有可能会增加。
3. CPU 执行指令的过程
3.1 编程语言的分类
现如今,编程语言分为三大类:机器语言、汇编语言和高级语言。
-
机器语言:以二进制(0和1)的形式来表示出的编程语言。它是计算机唯一能直接理解的语言,其他高级语言(如C、Python)需编译或解释为机器语言才能运行。
-
汇编语言:它是通过助记符(如
MOV、ADD)代替二进制操作码的一种语言。 -
高级语言:就是现在我们所学习的 Java、C++等,都属于高级语言。
高级语言经过编译、链接转化为汇编语言,汇编语言经过汇编后转化为机器语言。
而 CPU 所支持的语言,就是机器语言。
3.2 指令和指令表
指令就是指导 CPU 进行工作的命令,它主要是由 操作码 + 被操作数 组成(二进制)。操作码是用来表示要做什么动作,被操作数是本条指令要操作的数据。现在给出一个简化后的指令表。真正的指令表其实远要复杂得多:
|---------|----------------------------|----------|-------------------|
| 指令 | 功能说明 | 4位opcode | 操作的地址或寄存器 |
| LOAD_A | 从 RAM 的指定地址,将数据加载到 A 寄存器 | 0010 | 4位 RAM 地址 |
| LOAD_B | 从 RAM 的指定地址,将数据加载到 A 寄存器 | 0001 | 4位 RAM 地址 |
| STORE_A | 将数据从 A 寄存器写到 RAM 的指定地址 | 0100 | 4位 RAM 地址 |
| ADD | 计算两个指定寄存器的数据和,并将结果放在第二个寄存器 | 1000 | 2位的寄存器ID、2位的寄存器ID |
我们先来解释一下相关概念:
(1)RAM,即内存地址,我们可以想象成一个宿舍楼,每层都有一个长长的走廊,走廊上有很多的房间,每个房间都会有唯一的一个门牌号。号数会有0开始的、顺序的、连续的、依次累加的的特征。这些门牌号就被称为 "内存地址"。
(2)CPU的寄存器是处理器内部的高速存储单元,用于临时存放指令、数据和地址 ,给 CPU 起到一些辅助效果。其访问速度远高于内存和缓存,是CPU执行指令的关键组件。寄存器直接参与算术逻辑运算、控制程序流程以及管理内存访问。CPU 存储数据靠的就是寄存器。CPU 的成本也很高,掉电之后数据也会丢失。
(3)真实的一条指令是 8 个字节,即 64 bit;但这里我们规定,一条指令是 1 字节,即8 bit。
(4)一条指令由 4位操作码(opcode\函数名) + 4位操作数(函数参数)组成。
(5)设定数据 00 为寄存器A,数据 01 为寄存器B。
3.3 指令的执行过程
接下来来演示指令的执行周期,希望能通过该例子学习到计算机的基本工作流程。这里先给出一个 地址&数据表。
|----|----------|
| 地址 | 数据 |
| 0 | 00101110 |
| 1 | 00011111 |
| 2 | 10000100 |
| 3 | 01001101 |
| 4 | 00000000 |
| 5 | 00000000 |
| 6 | 00000000 |
| 7 | 00000000 |
| 8 | 00000000 |
| 9 | 00000000 |
| 10 | 00000000 |
| 11 | 00000000 |
| 12 | 00000000 |
| 13 | 00000000 |
| 14 | 00000011 |
| 15 | 00001110 |
CPU 执行指令,通常有以下流程:
(1)读取指令; (2)解析指令(对照指令表,理解指令的含义) (3)执行指令
现在我们假设 CPU 从 0 号地址开始执行:
第一步:从 0 号地址开始执行;
-
读取指令:00101110;[ CPU 内部有专门的寄存器负责保存读到的指令 ]
-
解析指令:0010 是 opcode;1110 是操作数。首先我们通过指令表可以知道 0010 属于指令LOAD_A,然后我们需要先将 1110 转化为十进制数,即等于地址14。不会算的可以使用计算器。所以整段指令就是,将地址 14 的数据 00000011 读取到寄存器 A 中。
-
CPU执行指令:寄存器A :00000011 寄存器B:目前为空
顺带一提,CPU 默认就是顺序执行指令。
第二步:执行1号地址;
-
读取指令:00011111;
-
解析指令:0001 是 opcode;1111是操作数。首先我们通过指令表可以知道 0001 属于指令LOAD_B,然后我们需要先将 1111 转化为十进制数,即等于地址15。所以整段指令就是,将地址 15 的数据 00001110 读取到寄存器 B 中。
-
CPU执行指令:寄存器A :00000011 寄存器B:00001110
第三步:执行2号地址;
-
读取指令:10000100;
-
解析指令:1000 是 opcode;0100 是操作数。首先我们知道通过指令表可以知道 1000 属于指令 ADD(计算两个指定寄存器的数据和,并将结果放在第二个寄存器)。需要注意,这里的 0100就不需要转化成十进制数了,而是需要将 0100 分为 01 和 00 两部分,这时假设 01 部分的寄存器编号是 B,00 部分寄存器编号是 A,所以该句 ADD 的含义就是,把 01 和 00 两个寄存器 A 和 B 中的数据相加,得到的结果放到 00 中。二进制加法,不会的可以使用Windows自带的计算器然后切换到程序员计算器即可。即 00000011 + 00001110 = 00010001。
-
CPU执行指令:寄存器A:00010001 寄存器B:00001110
第四步:执行3号地址;
-
读取指令:01001101;
-
解析指令:0100 是 opcode;1101 是操作数。通过指令表我们知道 0100 是 STORE_A,即将数据从 A 寄存器(00010001)写到 RAM 的指定地址。
-
CPU执行指令:寄存器A:00010001 寄存器B:00001110
第五步:执行4号地址;
-
读取指令:00000000;
-
解析指令:当出现 00000000 指令时。认为该程序结束。所以上述过程,其实就是 3 + 14 的运算过程。注意:程序结束后,寄存器中的数据也不会主动释放,除非有一个新的指令写入该寄存器并将值给覆盖掉,否则,该数据寄存器是不会主动释放的。
总结:
虽然上述过程看起来很复杂,步骤很多,但这是对于人脑来说的。由于计算机 CPU 一秒能处理 39 亿次,所以这点计算完全不成问题。
我们对于上述计算机的计算过程不需要精确掌握每个细节。只需要掌握:指令是什么?寄存器是什么,有何用?CPU执行指令的大概三个过程是什么?这三个问题能流利说出来即可。
4. 操作系统
操作系统就是一组做计算机资源管理的软件 的统称。我们常见的操作系统有:Windows、iOS、Android、Linux、鸿蒙 等等。如果是后端开发,那么之后接触的大概率是 Linux。各个操作系统之间的区别很多,最明显直接的区别就是这些系统提供的 API(系统函数)截然不同 ,这会导致同样的一个程序在 Windows 能跑,放到 iOS 上就跑不了。但是我们如果以后做开发,虽然程序都会部署到 Linux 上运行,但是开发阶段就在 Windows 上开发是完全没有问题的。
操作系统的作用主要有:
(1)通过系统内置的一些通用的驱动程序来间接管理各种硬件设备;
(2)给各种应用程序提供一个稳定的运行环境。即使在工作过程中某些软件出现 bug ,也不会影响其他的程序运行。
4.1 进程(process)
进程是操作系统对一个正在运行的程序的一种抽象,换言之可以把进程看做程序的一次运行过程。现在的计算机同时跑上百个进程,都是很常见的事,所以操作系统就需要能够很好的管理这些进程。简单来说,进程代表的就是正在执行的任务。
在操作系统内部,进程是操作系统进行资源分配的基本单位。
4.2 管理 / 创建 / 销毁 / 查看进程的过程
4.2.1 管理过程
从操作系统的视角来看,该如何管理进程呢?主要有以下过程:
-
首先先描述出一个进程的大概:使用结构体,描述出进程的核心属性 ;但是往往普通的结构体不能满足需求, 所以一般会使用进程控制块 。(进程控制块是非常大的结构体,以后简称 PCB)
-
再把多个进程组织起来。比如,Linux 这样的操作系统是使用了链表这样的形式,把多个 PCB 串在一起来管理。但不一定所有的操作系统都是用链表来管理的,为了统一,下面也会以 Linux 来举例。
4.2.2 创建 / 销毁 / 查看进程的过程
(1)创建一个新的进程:首先创建处于一个 PCB ,再初始化 PCB 上的各个属性,最后把 PCB 加到链表上即可。
(2)销毁一个进程:将该进程所对应的 PCB 在链表上找到,然后从链表上删除掉。
(3)查看进程列表:先遍历链表,一个个取出链表上的每个元素,再将里面的一些关键信息显示出来。
4.2 PCB 的属性
4.2.1 PCB 的基本属性
-
pid (即进程 id):它是进程的身份标识符。类似于学生的学号。
-
内存指针(它是一组指针 ):进程在执行时需要知道要执行的指令在哪里和指令依赖的数据在哪里 。例如:当你双击 exe 运行一个程序时,就会把 exe 中的指令和数据读取出来加载到内存中 。后续进程在运行过程中,就会从指令内存区域里一条一条的取指令并进行执行。
-
文件描述符表:进程在运行过程中,很多时候都会与硬盘这个硬件设备去打交道。硬盘上的数据就是以文件的形式来组织的,所以进程在读写文件的时候,就需要先 " 打开文件 ",将这些文件展示出来 。每次打开一个文件,就是把这个文件的信息保存到文件描述符表,表里的每个项就对应这一个打开了的文件。操作系统中会把很多资源都抽象成文件来表示,不一定是硬盘上的资源。
4.2.2 并发 / 并行执行
在操作系统进程的管理过程中,我们都知道一个 CPU 同一时刻只能执行一个进程的指令,那是不是在早期的计算机中进程只能一个一个去运行呢?
其实不是的, 我们能通过 " 分时复用 "的方法来解决不能同时运行多个进程的问题:分时复用,就是在一个单位时间内,将多个进程分为多份。即第一份是进程一的指令,第二份是进程二的指令......只要 CPU 运行的足够快,上述的切换过程也就会很快,这就使人感觉这些进程就是在同时运行一样。
这种把一个 CPU 核心上,按照分时复用执行多个进程的方式,称之为 " 并发执行 "。
那么随着时代的发展,CPU 的核心数越来越多,它就能真正做到同时执行多个进程。这种把多个 CPU 核心上同时执行多个进程的方式,称为 " 并行执行 "。
程序员写代码的时候,人的感觉和肉眼无法区分当前这些进程是" 并发执行 " 还是" 并行执行 "。所以我们也会把并行和并发,统称为 "并发" 。所以因为并发执行的存在,所以操作系统需要频繁的进行进程切换,这就是 "进程调度"。
4.2.3 PCB 的一些进阶属性
进程调度的进程运行时,是通过 PCB 的一些进阶的属性来调整调度。主要有以下几个:
(1)进程状态;进程有很多状态,但是典型的有两种,一种是就绪状态,一种是阻塞状态。
-
就绪状态 :指 " 随叫随到 ",就是进程可以随时到 CPU 上进行执行的状态;同时,正在执行的进程的状态也属于就绪状态。在操作系统中一般不区分就绪状态和执行状态,它们俩都属于就绪状态。
-
阻塞状态:指当前进程不适合到 CPU 上来执行的状态。这时这个线程将会暂时停止,只能达到了一定条件了之后才会继续执行。在这里我们先只做简单讲解,之后会有比较详细的讲解。
(2)进程优先级:计算机上通常都是几十上百的进程,那这么多进程它们之间到 CPU 上运行的机会是均等的么?其实不是,有些进程的优先级会高一些,从而吃到更多的 CPU 资源。例如游戏。
(3)进程的上下文:说白了就是存档和读档 。一个进程执行一段时间后如果进程调度失去 CPU 后,进程会通过上下文属性来 " 存档 ",下次执行时就会 " 读档 ",还会沿着上次执行到的状态继续往下执行,而不是从头执行。
- 保存上下文(存档):把 CPU 中的这些寄存器的值保存在内存中。(即 PCB 的对应属性中)
.2. 恢复上下文(读档):将 PCB 中刚才所保存属性,填写回对应的寄存器中以便给 CPU 来执行。
进程在 CPU 中运行的过程中, CPU 上的各种寄存器就表示了当前的进程运行的 " 中间状态 "。寄存器它不只是在存储中间结果,它还会存储当前进程执行到第几条指令以及当前进程中 " 函数调用关系 "。
(4)进程的记账信息:有统计的功能,它能统计每个进程都在 CPU 上运行了多久。如果某个时候发现某个进程有段时间没得到 CPU 资源了,进程调度就会给该进程倾斜一些资源。
本篇虽然是 JavaEE 初阶开篇内容,但是内容也不少,且基本都是概念。需要大家下来多加阅读一下,尤其是红字或加粗部分,以便于更好理解。
那么,本篇文章到此结束!希望能对你有帮助。