目录
1.操作系统原理
1.1 进程的概念
进程是操作系统管理任务的最小单位。除了进程,还有线程,线程是为了处理进程中更细小的任务,操作系统中的CPU为了处理线程中的任务需要经常切换,这就涉及到串行,并行,并发的概念。
串行:一个任务执行完毕,另外一个任务才能执行。
并行:在单位时间内,多个任务同时执行。现在的多核CPU支持多个任务处理。
并发:在单位时间内,只能处理一个任务,其他任务获取到CPU时间片就会开始执行另外一个任务,操作系统本身会进行用户态和内核态的切换。
1.2 进程的状态
1.3 进程的控制结构
进程内部通过一种PCB结构来存储相应的信息,PCB数据结构:进程管理,存储管理,文件管理。PCB是进程存在的唯一标识,进程消失,PCB也随之消失。
1.4 进程的切换
进程中一般存在的状态:新建,就绪,运行,阻塞,结束。在真实得java类Thread中通过以下6种状态控制:
NEW(新建),RUNNABLE(就绪),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(等待有限时间),TERMINATED(终结)线程切换从就绪到运行需要涉及到时间片得切换,需要牵涉到不同得线程调度算法
1.5 线程
线程是CPU调度得最小单位,一个进程可以有多个进程。多线程得切换不需要切换虚拟内存得资源,只需要切换线程私有得寄存器,程序计数器,虚拟机栈即可。
1.6 进程的调度
进程通过调度程序来控制哪些线程可以拿到时间片运行程序,哪些不能运行。常用得调度算法分为两种:非抢占式调度算法和抢占式调度算法。
调度得指标分为5种:CPU利用率,系统吞吐率,周转时间,等待时间,响应时间。
常用得算法:
先来先服务:哪个进程先来,先使用CPU时间片
时间片轮转调度:不管什么任务都使用同样得时间处理。
最短作业优先:哪个作业时间最短先处理哪个,如果有多个最短作业会导致长作业任务无法处理。
最短剩余剩余时间:哪个任务剩余时间最短处理哪个进程。
多级反馈队列调度:通过优先级和任务时间判断,分别处理。VIP客户在VIP通道处理。普通客户在普通通道处理。
1.7 进程间通信的概述
进程一般都有独立得用户空间和内存资源,一般是不可以互相通信,但是内核空间是共享得,我们可以通过内核空间进行通信。常用得进程通信得方式:内核空间,管道,信号量,信号,消息队列。
1.8 管道
管道在linux分为匿名管道和命名管道,匿名管道是单向得,输入完以后就会关闭,命名管道是双向得,在输入以后不会关闭只有读取以后才会关闭。管道得效率很低。
# 匿名管道
ps auxf | grep mysql
# 命名管道
mkfifo myPipe
# 管道输入
echo "hello" > myPipe
#开启其他shell窗口读取以后才会关闭输入窗口
cat < myPipe
1.9 消息队列
消息队列是内核空间得消息链表,我们只需要将消息通过定义发送方和接收方得消息体结构即可。消息队列是内核空间需要经过用户空间得copy才能通信。
1.10 共享内存
共享内存通过虚拟内存跟真实物理地址得映射,这样就不需要用户空间和内核空间得数据复制开销,大大加快了进程通信,但是在多个进程写入时可能出现覆盖得情况,导致数据不一致得情况。
1.11 信号量
信号量是一个整型的计数器,用来实现互斥和同步。
互斥信号量机制:我们将初始值设置为1,如果执行了P操作,将值改为0,代表内存共享可用,执行了V操作,将值改为-1,代表内存不可用,阻塞进程。
同步信号量机制:初始值设置为0,如果执行了P操作,将值改为-1,代表前一个进程还没生产数据,阻塞,如果执行了V操作,将值改为1,代表前一个进程生产完数据,可以执行。
1.12 信号
信号是进程中的一种异步通信机制,我们只需要输入相应的指令即可达到相应的命令。
kill -l 查看信号
kill -9 进程号 (杀死进程) 9对应sigkill信号。
1.13 socket通信
上述的几种进程通信方式都属于本机进程通信,如果需要跨网络我们就需要socket套接字来处理。socket内部的处理就需要IP协议://IP:端口号/路径的形式找到相应的机器进程通信。除了这种以外,java还提供了一种RMI通信,只需要继承remote接口即可实现两台java虚拟机进程的通信。
2.计算机网络协议
2.1 从一个HTTP请求来看网络分层原理
http请求要解决的问题
A主机->数据丢包->数据重复->数据完整性校验->数字转换模型信号->...->信号衰减->B主机。为了解决以上问题主流的两种网络分层模型:OSI-7层模型和TCP/IP协议模型。
2.2 HTTP协议
http协议是一个无状态的,以请求/应答方式运作的协议,它采用可扩展的语义和自描述信息格式,与基于网络的超文本信息系统交互。
它主要的组成部分:
1.起始行:描述请求行和响应行的基本信息
2.头部字段集合:以key-value形式描述基本信息
3.消息正文:以文本或图片,视频等形式的二进制数据组成。
2.3 HTTP请求的完整过程
平常访问的http地址:http/https://(ip:端口号/域名)/URI,http请求分以下几步:
1.DNS域名解析:在解析ip/域名的过程的过程中会先去浏览器缓存查找,如果是谷歌浏览器可以从chrome://net-internals/#events路径拿到浏览器缓存,如果能查到返回,不能查到就会去C:\Windows\System32\drivers\etc\hosts文件路径下查找,查不到就去DNS的根服务器查找,查不到回去国际DNS服务器查找,查到就进行下一步。
2. http三次握手协议,https还会有一次TLS握手。
3. 浏览器http请求应用服务器
4. 应用服务器返回http响应报文
5. 如果有其他请求重复3-4步,没有会生成一个DOM结构的树渲染页面
6. 第四次挥手:关闭链接。
2.4 TCP协议
TCP协议是一个面向连接,可靠的,基于字节流的传输层协议。
TCP协议传输过程:基于连接,数据传输之前要建立链接,客户端的数据是分段的请求,客户端发送给服务端的请求,服务端需要经历重排序保证与客户端的发送顺序一致,同时处理完毕需要发给客户端响应报文,如果客户端没有收到响应报文,经过一定的超时机制以后会重发报文。
TCP关闭链接过程如下:中间Time_wait状态(2MSL)等待2次接收发送报文时间的原因是需要确认服务端接收到ack报文,不会一直发送fin报文;还有一个原因是close_wait状态确认有未完成的数据不会造成下一次TCP连接数据紊乱。
TCP是一个基于字节流的协议,TCP不管是什么的数据包,只管传输,经过重排序和数据丢包重传的操作最后给应用程序的是客户端发送的字节流数据。
数据可靠性传输保证
1.如果ack报文没有收到会经过等待超时机制重传
2.如果请求报文没有收到会经过一次请求返回时间重传报文
3.上述两种方法比较消耗性能,为了节省性能,还有一种滑动窗口算法计算丢包的数据,通过确认最后一个字节数据包是否收到,如果没有收到,就去看前面连续的数据包是否收到,收到就不重传,没收到就重传后续数据包。
2.5 https协议
https协议的诞生是为了保证http协议的安全,因为http协议天生明文的特点,请求和返回报文都可能被篡改,https协议在http协议和tcp协议之间加上了一层SSL/TSL协议对请求和返回报文加密。
常用的加密算法:
1.摘要算法:MD5,sha1,sha2,sha1 256
2. 对称加密算法:加解密都是用相同密钥
3.非对称加密算法:加密使用公钥,解密使用私钥;如果加密使用私钥,解密使用公钥;
https保证安全原理如下:
1.http三次握手协议
2.加密套件选择,验证证书,生成随机密码,使用非对称加密算法获取密码
3. 使用对称加码算法传输保证性能。
我们可以通过查看证书设置查看证书的有效期,颁发者,使用的算法等信息。
3.数据结构与算法
3.1 数据结构与算法入门基础
如何判断一个数是2的N次方?
输入:2,4,8,16
输出:Y/N;
/**
* 计算一个数是否是2的N次方?
*/
public class Calculate2 {
/**
* 使用%求余算法
* @param n
* @return
*/
private static boolean calculate(int n){
boolean flag = false;
if(n>0){
if(n%2 == 0){
flag = true;
}
}
return flag;
}
/**
* 使用逻辑与&运算
* @param n
* @return
*/
private static boolean calculate2(int n){
boolean flag = false;
if(n <= 0){
return flag;
}
if((n & (n - 1)) == 0){
flag = true;
}
return flag;
}
public static void main(String[] args) {
System.out.println(calculate2(3));
}
}
3.2 算法特征,时间复杂度,空间复杂度业务场景
算法的五个特征:有穷性,确定性,可行性,有输入,有输出。
设计原则:正确性,可读性,健壮性
算法目的:高效率,低存储
算法标准:
时间复杂度(高效率):运行一个程序需要的时间
空间复杂度(低存储): 运行一个程序占用的内存
计算时间复杂度的业务场景:
1. 计算接口运行时间:常用的压测,冒烟,基本都是黑盒测试的范畴,测试人员对业务逻辑不懂或者数据不准确。可以自己计算时间复杂度。
时间复杂度表示法:大0表示法。O(N)/O(nlogn)
计算空间复杂度的业务场景:
1.找使用了内存的地方:比如Array,arrayList,linkedList,map,queue等地方。
import java.util.ArrayList;
import java.util.List;
/**
* 计算时间复杂度
* 常数:0(1)
* 对数:0(logN) 0(nlogN)
* 线性:0(n)
* 线性对数:0(nlogN)
* 平方:0(n^2)
* N次方:0(n^n)
*/
public class Calculate {
public static void main(String[] args) {
// * 常数:0(1)
int n = 1+1;//O(1)
int sum = 0;
for(int i =0 ; i<3; i++){
sum = sum+1;// 0(1):能确定跑几次的场景,不管数有多大都是O(1)
}
// * 对数:0(logN) 0(nlogN)
List list = new ArrayList();
n = list.size();//n未知
int i =1;
while(i <= n){
i = i*2;// 计算的值:2,4,6,8...2^n -->推导出2^n = i -->n=log2i-->计算机忽略常数-->logi-->O(logi)
}
for(int i =0 ;i<n;i++){
while(i <= n){
i = i*2;// 计算的值:2,4,6,8...2^n -->推导出n*2^n = i -->n=n*log2i-->计算机忽略常数-->n*logi-->O(nlogi)
}
}
// * 线性:0(n)
for(int i =0 ;i<n;i++){
sum = sum+1;// * 线性:0(n)
}
// * 线性对数:0(nlogN)
for(int i =0 ;i<n;i++){
while(i <= n){
i = i*2;// 计算的值:2,4,6,8...2^n -->推导出n*2^n = i -->n=n*log2i-->计算机忽略常数-->n*logi-->O(nlogi)
}
}
// * 平方:0(n^2)
//n未知
for(int i =0 ;i<n;i++){
for(int j = 0;j<n;j++){
sum = sum+1;// * 平方:0(n^2)
}
}
// * N次方:0(n^n)
for(int b =0 ;b<n;b++){
for(int i =0 ;i<n;i++){
for(int j = 0;j<n;j++){
sum = sum+1;// * 平方:0(n^3)
}
}
}
}
}
3.3 数组与链表
数组是一段相同元素的集合。存储的内存是连续内存。特点是下标和随机访问。插入,删除时间复杂度O(N),查找和更新时间复杂度O(1)。
ArrayList和数组:本质上都是数组,只是arrayList被jdk封装,不需要管扩容操作,array使用需要管所有的增删改查操作。
应用场景:不知道数组大小选用arrayList,知道大小的性能上选优可以使用数组。使用数组需要关注数组越界问题。
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* 计算一个5G数字的文件,输出文件中年龄有多少人?
*/
public class Calculate {
/**
* 生成14亿数字
* @param args
*/
private static void generateFile() throws Exception {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("D:\\系统默认\\桌面\\age.txt")));
for (int i=0;i<200;i++){//最大年龄200
for(int j=0;j<7000000;j++){
bufferedWriter.write(i);
}
}
}
/**
* 计算有多少数字
* @param args
* @throws Exception
*/
private static void calculate() throws Exception{
//新建数组存储
int[] ageArr = new int[200];
//得到文件
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\系统默认\\桌面\\age.txt"),"UTF-8");
BufferedReader bufferedWriter = new BufferedReader(isr);
String str = "";
int total =0;
while((str = bufferedWriter.readLine()) !=null){
Integer age = Integer.valueOf(str);
ageArr[age] ++;
total ++;
}
for(int i=0;i<200;i++){
System.out.println("年龄="+i+"有'"+ageArr[i]+"'个");
}
}
public static void main(String[] args) throws Exception {
// generateFile();
calculate();
}
}
3.3.1 堆,桟区别和面试题
java内存:堆内存和桟内存。区别:桟中存放引用数据类型和基本数据类型,堆中存放new的对象和数组。
int a=3;//先在桟中创建a变量,然后查看3在桟中有没有,有直接指向,没有创建一个3指向a;
public class Calculate {
public static void main(String[] args) throws Exception {
// String str1 = "abc";
// String str2 = "abc";
// System.out.println(str1 == str2);//true 都是桟中对象
// String str1 = "abc";
// String str2 = "abc";
// str1 = "bcd";
// System.out.println(str1+","+str2);//bcd,abc
// System.out.println(str1 == str2);//false 虽然str1和str2刚开始都指向abc,但后来str1指向变化以后不会变更str2
// String str1 = "abc";
// String str2 = "abc";
// str1 = "bcd";
// String str3 = str1;
// System.out.println(str3);//bcd
// String str4 = "bcd";
// System.out.println(str1 == str4);//true
// String str1 = new String("abc");
// String str2 = "abc";
// System.out.println(str1 == str2);//false str1在堆中new了一块内存
String s1 = "ja";
String s2 = "va";
String s3 = "java";
String s4 = s1 + s2;
System.out.println(s3 == s4);//false s3和s4值虽然相同,但是s4中+底层使用了stringBuilder封装,会new一个对象
System.out.println(s3.equals(s4));//true equals只比较值
}
}
3.3.2 为什么计算机编程语言要从下标0开始?
数组的内存是一组连续的内存。
a[0] = 10001; -> 10001+0*typesize;
a[1] = 10002;-> 10001+1*typesize;
a[2] = 10003;-> 10001+2*typesize;
如果从1开始,最后的计算需要使用到很多的寄存器等,比较麻烦。
a[1] = 10001; -> 10001+(1-1);
a[2] = 10002;-> 10001+(2-1);
a[3] = 10003;-> 10001+(3-1);
课外作业:二维数组的内存地址是什么样的?
一维数组寻址公式:init_loc(初始内存地址)+数组下标(index)*数组长度(size)
二维数组转化为一维:
1 2 3
4 5 6 -->123456->4的下标在二维里面是(1,0)->在一维里面的位置是3
->i(行)*n(一维的长度)+j(列)=1*3+0=3
a[i][j] i<n,j<m
loc = init_loc+(i*n+j)*size
3.3.3 链表
链表将一连串不连续的内存块串联在一起,为了将所有的节点串联在一起,每个节点上除了记录数据以外还要记录下一个内存块的地址。
特点:不需要连续的内存空间,有指针,常用的链表结构:单链表,双向链表,循环链表
单链表:比较特殊的是头节点和尾结点,头节点记录基地址,尾结点会指向一个Null,表示尾结点。应用场景:linkedlist
查找:next指针指向下一个节点。
插入:头,中间,尾部。插入时间复杂度O(N)
删除:删除头尾指针引用,next指针指向下一个节点。
循环链表:与单链表相似,唯一不同的地方是尾结点的next指针指向头节点的头部,形成一个首尾相连的链表。应用场景:约瑟夫问题,转圈圈问题。
双向链表:每个节点中间都有一个双向指针,尾节点next指针指向头节点的头部,形成一个首尾相连的链表。应用场景:B+树
架构师角度:选择最适合的技术,红黑树和链表的时间复杂度logN,O(N).
稀疏数组:针对多维,但一般使用链表代替。a[3][4] = 12空间。
单链表实例
/**
* 单链表结构
*/
public class LinkedList {
private ListNode head;//头节点
private int size = 0;
/**
* O(1)
* @param data
*/
public void insertHead(int data){//插入头结点
ListNode newNode = new ListNode(data);//新建节点
newNode.next = head; //桟内存的引用
head = newNode;
}
/**
*O(N)
* @param data 数据
* @param position 位置
*/
public void insertNth(int data,int position){//插入中间节点
if(position == 0){
insertHead(data);
}else {
//遍历到position指针
ListNode current = head;
for(int i =1 ;i<position; i++){
current = current.next;// 相当于C++ p = p-> next;写法
}
//插入节点
ListNode newNode = new ListNode(data);
//新指针指向后一个节点保证不断链
newNode.next = current.next;
//当前节点指针指向新节点
current.next = newNode;
}
}
public void deleteHead(){//删除头节点
head = head.next;
}
public void deleteNth(int position){//删除中间节点
if(position == 0){
deleteHead();
}else{
//遍历到position指针
ListNode current = head;
for(int i =1 ;i<position; i++){
current = current.next;// 相当于C++ p = p-> next;写法
}
//删除
current.next = current.next.next;//next指向后一个节点,再指向后一个节点
}
}
/**
* 查找链表数据
* @param data
*/
public void find(int data){
ListNode current = head;
while(current != null){
if(current.value == data) break;
current = current.next;
}
}
public void print(){
ListNode current = head;
while(current != null){
System.out.println(current.value+" ");
current = current.next;
}
System.out.println();
}
}
class ListNode{
int value;//数据
ListNode next;//下一个指针
public ListNode(int value){
this.value = value;
this.next = null;
}
}
双链表实例
public class DoubleLinkedlist {
private DoubleNode head;//定义一个头
private DoubleNode tail;//定义一个尾
DoubleLinkedlist(){
head = null;
tail = null;
}
//插入头节点
public void insertHead(int data){
DoubleNode newNode = new DoubleNode(data);
if(head == null){
// head = newNode;
tail = newNode;
}else{
//将head节点的pre指针指向新节点
head.pre = newNode;
//将新节点的next指向head节点
newNode.next = head;
//将head节点给新节点
// head = newNode;
}
head = newNode;
}
}
class DoubleNode{
int value;//数据
DoubleNode next;//下一个指针
DoubleNode pre;//前一个指针
public DoubleNode(int value){
this.value = value;
this.pre = null;
}
}
3.3.4 课后作业:如何设计一个LRU淘汰算法?
维护一个单链表,新加一个插入到头部,定义有限内存空间,如果有空间插入头部,如果没有空间,删除最后一个即可。
3.3.5 数组和链表的区别
3.4 桟
桟是一种后进先出(LIFO(last in First Out))的数据结构。
桟的实现:数组和链表
桟的分类:1.以数组实现,数组头作为栈底,数组头到尾作为桟的生长方向。数组先定义大小,不容易出现内存溢出。
2. 以链表实现,以链表头作为栈底,链表头到尾作为桟生长方向。天生支持动态扩容,但不停的插入头部,会造成内存溢出。
/**
* 桟实现接口
*/
public interface MyStack<Item> {
MyStack<Item> push(Item item);//出栈
Item pop();//入栈
int size();//大小
boolean isEmpty();//是否为空
}
/**
* 使用数组实现桟
* @param <Item>
*/
public class ArrayStack<Item> implements MyStack<Item>{
//定义数组
private Item[] object = (Item[]) new Object[1];
//定义数组初始大小
private int size;
public ArrayStack(int cap){
object = (Item[]) new Object[cap];
}
@Override
public MyStack<Item> push(Item item) {//入栈
judgeSize();
object[size ++] = item;
return null;
}
@Override
public Item pop() {//出栈
if(isEmpty()){
return null;
}
//将栈顶取出
Item item = object[--size];//--size先减后赋值 size--先复制后减
//将栈顶内存释放
object[size] = null;
return item;
}
@Override
public int size() {
return size;
}
//判断是否需要扩容
private void judgeSize(){
//如果数组长度大于元素个数,扩容
if(size > object.length ){
resize(2 * object.length);
}else if(size > 0 && size <= object.length/2){//如果元素个数大于0且元素个数小于等于数组大小的一半,缩容
resize(object.length/2);//手动减少内存浪费
}
}
//数组扩容
private Item[] resize(int size){
Item[] temp = (Item[]) new Object[size];
for (int i =0;i < size;i++){
temp[i] = object[i];
}
return temp;
}
@Override
public boolean isEmpty() {
return size == 0;
}
}
3.4.1 括号匹配
括号匹配的思想不理解,不知道有什么问题,明天再看一下。
import java.util.Scanner;
/**
* 括号匹配
* 案例值:[]{}
* [[]
* [{}]{}
*/
public class KuohaoStack {
public static boolean isOk(String s){
//定义一个桟元素
MyStack<Character> brackets = new ArrayStack<Character>(20);
//遍历元素
char[] c = s.toCharArray();
Character pop;
for(char x: c){
switch (x){
case '{':
case '(':
case '[':
brackets.push(x);
break;
case '}':
pop = brackets.pop();
if(pop == null) return false;
if(pop == '{'){
break;
}else{
return false;
}
case ')':
pop = brackets.pop();
if(pop == null) return false;
if(pop == '('){
break;
}else{
return false;
}
case ']':
pop = brackets.pop();
if(pop == null) return false;
if(pop == '['){
break;
}else{
return false;
}
default:
break;
}
}
return brackets.isEmpty();
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String next = scanner.next();
System.out.println("匹配结果:"+isOk(next));
}
}
}
3.4.2 加减乘除计算
用桟实现一个简单的四则运算:3+11*2-8-15/5,用桟实现这个算术表达式
1. 算法思想:用两个桟来实现,一个存储数字,一个存储符号。
有数字来时放入数字桟,有符号来时放入符号桟,如果当前符号跟栈顶的符号匹配优先级低于或者等于已有的符号,就先把栈顶的符号拿出来进行计算,并把计算后的值放入数字桟。
可以使用桟实现一个简单的计算器。
3.4.3 如何设计一个浏览器的前进和后退功能
算法:两个桟,一个用来存储前进和新页面,一个用来存储后退页面,当没有新页面时,点后退把元素出栈到前进桟,有新页面时,清空后退桟,前进桟中存储新页面,后退桟就不可以后退。
3.5 队列
队列:是一种特殊的线性表,只允许一端删除,一端插入,删除的一端为队列头,插入的一端为队列尾,没有元素称为空队列。
队列的特点:1.线性表 2.先进先出FIFO(First in First Out)
队列的分类:
1.单向链表:一端插入,一端删除
2.循环链表:每一端都可以插入和删除
队列的基本操作:出队dequeue和入队enqueue
队列的实现方式:1.使用数组实现一个队列
循环列表问题:
1.怎么判断队列已经满了?
解决方法:1.加一个实际队列大小变量size 2.(tail+1)%n==head
/**
* 使用数组实现一个队列
*/
public class ArrayQueue {
//定义一个队列
private int[] data; //数据大小
private int head = 0; //头
private int tail = 0; //尾
private int size = 0; // 队列大小
public ArrayQueue(int cap){
data = new int[cap];
size = cap;
}
//入队操作
public void push(int m){
if(tail == m)
//存在一个问题:在移动的时候会浪费已经使用过的内存,导致使用过的内存无法使用。
//解决方法:可以使用复制,当尾部节点等于size时将数组复制到前面
//2.使用循环队列解决
return;
data[tail] = m;
tail++;
}
//出队操作
public int pop(){
//判断为空
if(isEmpty()){
return -1;
}
int m = data[head];
head ++;
return m;
}
private boolean isEmpty(){
if(head == tail)
return true;
return false;
}
}
/**
* 使用数组实现一个循环队列
*/
public class CircleArrayQueue {
//定义一个队列
private int[] data; //数据大小
private int head = 0; //头
private int tail = 0; //尾
private int size = 0; // 队列大小
public CircleArrayQueue(int cap){
data = new int[cap];
size = cap;
}
//入队操作
public void push(int m){
if((tail +1) % size == head)
//存在一个问题:在移动的时候会浪费已经使用过的内存,导致使用过的内存无法使用。
//解决方法:可以使用复制,当尾部节点等于size时将数组复制到前面
//2.使用循环队列解决
return;
data[tail] = m;
tail = (tail+1) % size;
}
//出队操作
public int pop(){
//判断为空
if(isEmpty()){
return -1;
}
int m = data[head];
head = (head +1) % size;
return m;
}
private boolean isEmpty(){
if(head == tail)
return true;
return false;
}
}
3.5.1 课后作业
使用链表实现一个单向队列
3.5.2 优先队列
应用场景:抽奖,普通用户,vip客户,svip客户。
在循环队列入队时排序即可。
3.5.3 阻塞队列
阻塞队列跟单向队列相似,不同点是队列为空时取数据会被阻塞,队列满时插入数据会被阻塞。
3.5.4 线程池问题
线程池里面当任务满时,又来一个任务,会采取什么样的策略?这些策略又是如何实现的?