操作系统与网络知识
一. 进程与线程
1. 区别和联系:
进程是系统资源分配的最小单位,线程是程序执行的最小单位,一个进程下可以启用多个线程
进程使用独立的数据空间,而线程共享进程的数据空间
2. 线程调度:
- 时间片轮转调度:系统将所有的就绪线程按先来先服务算法的原则,排成一个队列,每次调度时,系统把处理机分配给队列首线程,并让其执行一个时间片,结束后将之中断并送至队尾
- 先来先服务调度:先来先服务,一旦一个线程占有了处理机,它就一直运行下去,直到该线程完成工作或者因为等待某事件发生而不能继续运行时才释放处理机。
- 优先级调度:例如Windows进程有32个优先级,高优先级进程优先运行,只有高优先级进程不运行时,才调度低优先级进程运行,优先级相同的进程按照时间片轮流运行。
- 多级反馈队列调度:Unix,线程位于个个队列内,其中各个队列对于处理机的优先级是不一样的,不同队列间采用优先级调度,同一队列间采用先来先服务调度,最末队列采用时间片轮转调度,综合了上面3个调度的所有功能。
windows使用优先级+抢占调度算法 java使用抢占调度算法,与之相对的有协同式调度,线程执行完才切换,不可控不安全。
3. 线程切换步骤:
- 线程上下文切换:
无论是单核还是多核CPU都是支持多线程代码的,CPU通过给每个线程分配CPU时间片来实这个机制。CPU不停地切换线程执行,在切到别的线程时把当前线程的信息保存下来,在执行别的线程时加载对应线程的信息,这一保存-加载过程就是线程的上下文切换 - 切换损耗:
1 直接损耗 上述CPU寄存器保存和加载过程的损耗,锁竞争
2 间接损耗 多核共享数据导致的损耗 - 避免切换损耗:
1 无锁并发编程: 多线程竞争锁时,会引起上下文切换,
2 CAS算法,Java的concurrt包下的Atomic类使用CAS算法来更新数据,不需要加锁。
3 避免创建不必要的线程
4. 引申:CAS
CAS:Compare and Swap,即比较再交换。CAS是一种无锁算法,可以保证多线程下的原子性实现。CAS有3个操作数,内存值V,旧的预期值O,要修改的新值N。伪代码为
do{
备份当前内存中值V到旧数据O;
基于旧数据O构造新数据N;
}while(内存值V = 新数据N)
如果内存地址与新地址一致则操作成功,如果不一致说明被别的线程修改了数据,重复一上操作(CAS是基于乐观锁的理念,既假设线程并发并不会那么剧烈。由于CAS是计算机指令操作,速度非常快,在低并发的情况下会有很好的性能表现)
CAS的ABA问题:
CAS可能出现ABA问题,既一个线程在读取共享变量后如果被修改后又被改回来,那么CAS无法得知他已经被修改,这可能会出现问题:
- 银行账户有100块钱,此时有A,B 两人同时取款100,理应只有1个人取款成功,银行余额变为0。
- 假设 A 取款成功,而 B 的取款被阻塞了,此时有 C 汇了100进来,银行余额恢复到100
- C汇款完毕后,B的取款恢复了,它比较了下预期值和之前一样为100,就将余额置0,产生错误,他执行了不应该执行的操作。
ABA问题的补救方法:
给内存中的值加上版本号,我们现在不但要比较预期值,还要比较版本号,Java中的AtomicStampedReference类就实现了用版本号做比较的CAS机制。
二. 线程与通信
1. 创建线程的三种方式
继承Thread类创建线程类,调永start方法启动线程
性能开销,单继承,功能少,维护困难
重写Runnable的抽象方法并传入new Thread(runnable),可以用局部内部类实现
面向接口开发,扩展好
实现Callable接口并实现call方法,拥有返回值,可以使用FutureTask为来模式接收。
拥有返回值,future模式,异步
常用方法:
- sleep(int): 休眠 ,默认毫秒,持有锁
- wait(): 等待 释放锁。
- join(): 进入阻塞状态直到别的线程运行完毕,释放锁
- setPriority(int): 设置优先级,初始5,范围1-10
- .yield(): 回到就绪状态,继续强占时间片
- .setDaemon(true): 守护线程,只有其他线程结束才结束
- .interrupt():中断睡眠或中断运行,必须捕获异常
- .stop(): 结束线程,可能出现异常不推荐使用
2. 线程的生命周期
六个状态对应ThreadState的枚举
- new:创建
- runnerbale:可运行,此时并不能运行会自动进入下一状态
- reader:准备状态
- running:运行中
- block:阻塞状态,锁竞争失败
- sleep:等待状态 ,有超时和无超时的等待
- terminated:结束 线程执行结束
3. 线程池
由于不断的创建-结束线程相当占用系统资源,为了节省CPU和内存资源,引申出了线程池的概念。
线程池就是首先创建一些线程,它们的集合称为线程池,这些线程在使用完毕后回归还线程池,到达线程复用的目的。
自定义线程池
Executor还支持自定义线程池,通过new ThreadPoolExecutor(参数..)可以自定义线程池
参数 | 说明 |
---|---|
corePoolSize |
核心线程数,多余线程会放入等待队列中 |
maximumPoolSize |
**当等待队列满时,会创建线程直到最大线程数为止(一般来说不要超过cpu核心2) |
keepaAliveTime |
多余线程在线程池中存在的时间。 |
timeUnit |
keepaAliveTime存活时间单位,枚举等 |
workQueue |
阻塞队列,如ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue等 |
threadFactory |
可定制线程,一般使用默认即可 |
rejectedHandler |
线程池满时拒绝策略,如Abort,Discard,CallerRuns,DiscardOldest |
WorkQueue: 从上到下吞吐量依次提升
- ArrayBlockingQueue:有界的阻塞队列,它的底层是一个数组,通过一个可重入锁来保证操作线程安全
- LinkedBlockingQueue:无界的阻塞队列,基于链表结构实现,它的底层使用两把锁实现线程安全,分别作用于队首和队尾
- SynchronousQueue:一个不存储元素的阻塞队列,一个插入对应一个移除,起到一个传递的作用,类似于管道,能够最快地创建线程执行任务
java的Executors线程框架提供了5种常用的线程池:
线程池 | 说明 |
---|---|
Executors .NewFixedThreadPool |
固定线程数,无界队列.适用于任务数量不均匀的场景,对内存压力不敏感,但系统负载敏感的场景. |
Executors .newCachedThreadPool |
无限线程数,适用于要求低延迟的短期任务场景. |
Executors .newSingleThreadPool |
单个线程的固定线程池,适用于保证异步执行顺序的场景. |
Executors .newScheduledThreadPool |
适用于定期执行任务场景,支持固定频率和固定延迟. |
Executors .newWorkStealingPool |
使用ForkJoinPool,多任务队列的固定并行度,适合任务执行时长不均匀的场景.务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 |
Executors提供的线程池在线程池满时,可以配置4种拒绝策略:
拒绝策略 | 说明 |
---|---|
AbortPolicy |
直接抛出RejectedExecutionException拒绝异常,程序继续执行 |
DiscardPolicy |
丢弃任务,不给予任何处理 |
DiscardOldestPolicy |
丢弃最老或者说最早插入队列的那个请求,尝试再次提交当前任务 |
CallerRunsPolicy |
丢弃线程并将将线程交由调用者执行,如main方法 |
线程池任务执行流程
判断核心线程数,如果小于则创建线程执行
判断线程队列是否已满,如果没满则进入队列执行
判断是否已经达到最大线程数,如果小于则创建线程执行,大于则调用拒绝策略
4. 线程通信
为什么需要线程通信?
线程是操作系统中独立的个体,如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一。
4.1 线程通信的实现方式
- 通过synchronized和Thread,通过调用Thread的 wait()和notify()方法实现
- 通过volatile的可见性来实现
- 通过lock和Condition的await()和signal()方法实现
- 利用blockingQueue阻塞队列的put(),take()阻塞方法来实现
- 通过java的同步辅助类来实现(CyclicBarrier等待所有线程就绪,计数器为0时)
4.2 volatile 修饰符
volicate规定在变量修改后会强制写入主存并废弃其他线程的缓存,防止指令重排。
volatile规定在读取变量时会通过缓存一致性协议来检测变量是否被修改。一旦线程在读取变量后处于阻塞状态,此时其他线程修改变量的值,由于已经过了读取的阶段,缓存一致性协议无法得知该变量已修改,无法保证原子性。
4.3 ThreadLocal本地线程变量
ThreadLocal为变量在每个线程中都创建了一个副本,利用set()和get()方法每个线程可以操作自己内部的副本变量,唯一的问题是必须考虑占用的内存。
可以将ThreadLocal泛型为ConcurrentHashMap,加快查询效率。
四. Linux知识
1. 常用命令
命令 | 例子 | 介绍 | |
---|---|---|---|
cd | cd ./path | 切换到当前目录 | |
cd ./path | 切换到上层目录 | ||
ls | ls -l | 列出目录下所有文件详情,包括属性和权限德等 | |
ls -a | 包括.XX的隐藏文君 | ||
ls -h | 文件大小自动转换GB,KB等 | ||
cp | cp filename filepath | 复制文件 | |
cp -r | 目录的递归复制 | ||
mv | mv filename filepath | 复制文件/目录 | |
mv file1 file2 filepath | 复制多文件/目录 | ||
tar | tar -czvf test.tar.gz a.c | 压缩 a.c文件为test.tar.gz | |
tar -xzvf test.tar.gz a.c | 解压文件 | ||
zip | zip 1.zip 1.txt | 压缩 1.txt | |
zip -r | 压缩目录 | ||
unzip | unzip wwwroot.zip | 解压文件/目录 | |
zip wwwroot.zip -d path | 解压到指定目录、 | ||
chmod | chmod 777 file | 修改文件权 读4写2执行1(用户/用户组/其它人) | |
grep | cat test.yml ` | ` grep root | 结合管道搜索行 |
less | cat test.yml ` | ` grep root | 结合管道查看文本内容 可以随意浏览 |
find | find ngnix | 查找文件 |
(更多还有netstat ,vi,vim等)
2. 权限
权限符号:
符号 | 权限 | 数字 |
---|---|---|
r | 读 | 4 |
w | 写 | 2 |
x | 执行 | 1 |
权限说明:
rw-r—r—
用户/用户组/其他人
五. 网络知识
5.1 HTTP
- 需要知道协议中的Method,Header,Cookies.
- 知道常见状态码含义
- 了解HTTPS的交互流程
- QUIC基于UDP实现原HTTP功能,现已被标准化为HTTP3协议
5.2 TCP
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP的传输参数
- 3个标志位
- 确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
- 同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
- 终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
- 2个序列号
- 序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。客户机受到ack后会将ack作为seq发送给server
- 确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
总结:
seq同步序列号用来区分请求,每次都不一样,客户机回复的seq = 受到的ack
ack编码用来确定请求是否受到,ack = seq+1
TCP的三次握手
服务器在启动后处于监听状态,在客户机和服务器建立联系的时候
- client 首先发送一个syn(Synchronize Sequence Numbers)同步序列编号和seq序号给 server
- server 收到后加上ask响应一起发回给 client
- client 收到后单独发送ask响应給 server说明连接完成
syn洪水攻击
syn洪水攻击会向server发送大量不明来源的syn同步序列号
TCP连接在第二次握手的时候,服务器会给客户发送ask响应并等待第三次握手,结果一直无法找到客户机。大量的这种半连接会造成CPU和服务器资源的极大损耗。
防御策略
- 修改linux的tcp等待时间,
- 修改linux的最大客户端syn数量
- 启用syn cookies等等
TCP的四次挥手
- 先由client向server发送一个FIN请求中断连接。
- 当server接收到FIN时,向客户端发送一个ACK响应,说明收到
- server向client发送一个FIN,告诉客户端可以中断连接
- client收到FIN后发送ASK给服务器,等待了一段时间后彻底中断连接
5.3 Cookie 和 Session
HTTP请求是无状态的,每一次请求都是一次新的请求,与上一次无关。Cookie 和 Session就是为了解决HTTP请求的无状态问题,用来保存用户数据。
5.3.1 Cookie
Cookie区分域名并保存在客户端,通常来说每个域名可以拥有多个Cookie,每个Cookie大小不超过4KB。
实现细节:
- client 发送 request 给 server ,从本地读取Cookie信息 放在 request header
- server 收到后将用户信息保存在 response header 中的Cookie信息中
- client 收到后将Cookie保存在本地
5.3.2 Session
Session 保存在服务器端,每个回话都会创建一个session
实现细节:
server 收到 client 的请求后将用户用户信息保存到会话session中
5.3.3 Cookie 和 Session 的区别**
- Cookie储存在本地不安全,而Session 存储在服务端
- Cookie的大小限制于浏览器实现,而Session 大小仅限制于server内存
- Cookie的销毁依靠过期时间,而Session 不但有过期时间,而且会在会话结束后消除
- Cookie仅能储存Unicod码和二进制码,而Session 可以储存大部分数据类型
- Session基于Cookie而存在
5.4 4/7层网络模型
互联网的实现,分成好几层,每一层都有自己的功能并靠下一层支持。
OSI七层开放式系统互联通信参考模型它是一个由国际标准化组织提出的概念模型,试图提供一个通用的互联网标准框架。而TCP/IP网络模型参考了OSI七层模型,去除了物理层,并将应用层、表示层、会话层合并为一个应用层,组成4层模型。
总结:
- 物理层:负责传送0和1的电信号,主要是电缆和双绞线
- 链路层:用来规定电信号的分组方式,以太网协议(桢),MAC地址
- 网络层:建立主机到主机的通信,广播,路由,ARP地址解析协议,IP地址
- 传输层:建立线程到线程的通信,端口号,UDP/TCP
- 会话层:建立和中断连接,管理数据传输
- 表示层:数据传输格式和网络传输格式的切换,图片,视频,邮件等
- 应用层:消除设备固有数据格式和网络传输格式的差异,HTTP,SSH,FTP协议等
- 物理层也就是光缆和wifi,传输计算机能够是别的0/1信号
- 链路层在物理层上方,规定了0/1的分组方式,一组电信号叫一帧,由head和data组成,每个网卡都有唯一MAC地址
- 链路层规定了数据传输的方式,但是如何传输由网络层来决定。互联网是由一个个子网组成的大网,如果mac地址在同一个子网内使用广播方式发送数据,反之使用路由。为了区分mac是否在同一子网内,引入了ip的概念,根据ip协议不同氛围IPV4和IPV6,0.0.0.0 - 255.255.255.255 每个子网最多保函255台主机
- 有了网络已经可以进行计算机通信了,但是一台计算机上有多个线程,为了
5.4.1 物理层
物理层的作用就是通过物理手段把电脑连接起来,它主要规定了网络的一些电气特性,作用是负责传送0和1的电信号。(光缆,WIFI等)
5.4.2 数据链路层
链路层在物理层的上方,规定了了0和1的分组方式。(以太网协议,MAC地址)
以太网协议规定:一组电信号构成一个数据包,叫做“帧”,由 Head和Data组成 MAC地址规定:每块网卡出厂的时候,都有全世界独一无二的MAC地址(12个16进制数) 网卡通过局域网广播的方式发送帧数据
5.4.3 网络层
- 互联网是由一个个子网组成的更大的子网,而广播仅仅发生在局域网子网内。如果MAC地址处在同一个子网内,那么使用广播方式发送数据,反之使用路由。
- 为了区分MAC是否在同一个子网内,引入了网络地址的概念,也即IP地址。
- 根据IP地址协议的不同,有IPV4 和IPV6两种,IPV4就是我们日常使用的4段十进制IP 0.0.0.0 - 255.255.255.255。
- 互联网上的每台计算机都会分配到一个IP地址,前3位为网段地址,最后一位为主机号,一个子网内最多保函255台主机。路由使用子网掩码 255.255.255.0 与 IP地址向与用来判断是否处于同个子网。
- 不同的子网之间使用路由连接,路由拥有多个网卡,维护着一个IP-MAC地址的列表,既ARP协议
5.4.4 传输层
- 有了网络层之后我们已经可以进行计算机间的通信了,但是一台计算机上有多个线程,为了区分哪些线程使用了网络,引入了端口号的概念。
- 端口号是16位2进制数,范围从0到65535。
- 进程可以通过建立Socket连接进行端口到端口的网络通信,也可以实现主机内部的端口通信。
- UDP和TCP协议都是规定Socket如何实现的协议,UDP无需建立连接速度快一对多,而TCP需3次握手安全一对一
5.4.5 应用层(会话层/表示层/应用层)
TCP/IP 的应用层由三部分组成
- 会话层:主要负责建立/断开通信连接,以及数据的传输管理
- 表示层:数据格式和网络传输格式的切换,如(图像,视频等)
- 应用层:针对特定应用的协议(HTTP,SSH,FTP等)
传输层已经实现了进程到进程的通信,以为不同的应用或者说进程传输数据,例如图片,邮件,视频等。应用层的作用,就是消除设备固有数据格式和网络标准数据格式直接的差异。
网域名称系统DNS(Domain Name System)
由于使用IP进行访问不太方便,引出了网站域名的概念。它作为域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。
5.4.6 自上而下的数据包结构
应用层数据包(应用层) -> TCP/UDP数据包(传输层) -> IP数据包(网络层) -> 以太网数据包(链路层,最大1500)
整个网络通信的数据包封装解封的流程如图所示:
六. JSP
1. 九大隐式对象和四大域对象
四大域对象(小到大):
- PageContext 域:访问JSP页面时创建,结束销毁,作用在页面中
- request 域 :request对象,在整个请求过程中
- session 域 :第一次执行request.getSession()时创建直到web关闭,默认30分钟有效期
- ServletContext域:服务器启动后创建,服务器关闭或web应用移出容器
九大隐式对象:
隐式对象 | 说明 |
---|---|
request | 转译后对应HttpServletRequest/ServletRequest对象,请求对象 |
response | 转译后对应HttpServletRespons/ServletResponse对象,客户端响应 |
pageContext | 转译后对应PageContext对象,页面的上下文,包含其他8个对象 |
session | 转译后对应HttpSession对象,基于cookie的服务器会话 |
application | 转译后对应ServletContext对象,JSP上下文信息 |
out | 对应java的Java.io.Writer,用于输出流到客户端 |
page | 转译后对应this,代表当前页面的servlet实例 |
exception | 转译后对应Throwable对象,页面异常才会创建,可以在错误页使用 |
config | 转译后对应ServletConfig对象,servlet初始化参数 |
六. 面试题
为什么连接的时候是三次握手,关闭的时候却是四次握手?
当Server端收到FIN报文时很可能并不会立即关闭连接,所以先回复ASK
为什么4次挥手的TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
因为网络是不可靠的,假如client最后发送的ASK没有被server收到,server会一直重复第三次挥手,所以需要等待保证ASK能被发送到服务器,从而中断连接
为什么不能用两次握手进行连接?
为了保证双方都做好了准备,如果客户机不发送第三次连接,服务器无法得知客户机状态,会拒收client的数据
如果已经建立了连接,但是客户端突然出现故障了怎么办?
server会实行心跳监测机制保证连接的alive
HTTP和TCP是什么,它们有什么联系?
- TCP是基于字节流的传输层通信协议,它的可靠性通过3次握手和4次挥手来实现
- HTTP协议应用层的一种web传输规范,基于TCP来实现。HTTP要求每次请求必须要有响应,并在响应结束后断开连接。
Get和Post的区别?
HTTP协议有6种请求方法,规定了request发送的方式,其中的核心方法是Get和Post,它们的区别在于:
GET
把参数包含在URL中不安全,POST
通过request body传递参数GET
在浏览器回退时是无害的,而POST
会再次提交请求。GET
请求会被浏览器主动cache,而POST
不会,除非手动设置。GET
请求参数会被完整保留在浏览器历史记录里,而POST
中的参数不会被保留。GET
请求在URL中传送的参数是有长度限制的,而POST
没有。- 对参数的数据类型,GET只接受ASCII字符(中文乱码),而
POST
没有限制。
如何实现生产者消费者模型(线程通信)?
可利用锁,信号量,线程通信,阻塞队列等方法实现
如何理解线程的同步与异步,阻塞与非阻塞?
- 同步和异步关注的是消息通信机制
同步进程在请求后会一直等待请求的结果,然后执行,而异步线程再请求后继续运行 - 阻塞和非阻塞关注的是线程在等待调用结果时的状态
阻塞进程在等待请求结果时执行流程停止,而非阻塞线程在等待结果时保存着执行状态
- 同步和异步关注的是消息通信机制
线程池处理任务的流程?
coresize->等待队列是否满?-> maxsize?
wait与sleep有什么不同?
wait是Object方法,sleep是Thread方法 wait会释放锁,sleep不会 wait要在同步块中使用,sleep在任何地方使用 wait不需要捕获异常,sleep需要
Synchronized与ReentrantLock有什么不同,各适用什么场景?
实现机制,性能,功能性(公平,condition,等待锁)
读写锁适用与什么场景,ReentrantReadWriteLock是如何实现的?
ReentrantReadWriteLock重入读写锁是基于AQS,或者说抽象的同步等待队列思想来实现的。ReentrantReadWriteLock有共享锁和非共享锁,读锁是共享锁,写锁是非共享锁,读读共享,读写互斥,写写互斥。 ReentrantReadWriteLock的内部维护了一个同步队列和等待队列,以及状态码state。state为1时说明发生了锁互斥,进来的线程会被划分到等待队列,如果ReentrantReadWriteLock被指定为公平锁,那么在写进程后的都线程也会被放入队列中(读读写读)。
保证线程安全的方法有哪些?
CAS,synchronized,lock,ThreadLocal
如何尽可能提高多线程并发性能?
减少临界区范围 使用ThreadLocal 减少线程切换 使用读写锁或CopyOnWrite
ThreadLocal用来解决什么问题,ThreadLocal是如何实现的?
不是用来解决多线程共享变量问题,而是用来解决线程数据隔离问题
七. 加分项
结合相机项目经验或实际案例介绍原理
自己项目的线程设置场景
解决多线程问题的排查思路与经验
- 熟悉常用的线程分析工具与方法,如Jstack
- 了解Java8对JUC的增强, 用LongAdder替换AtomicLong,更适合并发度高的场景
- 了解Reactive异步编程思想