JDK java develop’s kit java开发工具包,包含了JRE和常见开发工具javac
JRE 包含了JVM 和 核心类库
首先equals()是Object类下的一个方法,比较的是 引用类型。默认比较引用地址,重写比较其他东西。
其次,== 引用和基本数据类型都能比较 引用比较的是地址,基本数据类型就是值
首先,这两个方法没有联系
其次,对于传参是地址不一定,因为hashCode是根据地址值经过特定算法运算得出的一个值,有可能不同地址hash出来的值是一样的。
如果equals()重写了,基本都不一样。
首先 final 可以修饰 变量 方法 类
修饰变量的时候 使得变量不可以二次赋值
修饰方法 方法不能被重写
修饰类 类不能被继承
String是引用类型,根父类是Object 本质是一个 static final char[]
String 不可变,new 和 赋值的区别 ,new的话会创建两个字符串对象,还有个堆内存,不管常量池有没有。 = 会先去常量池找,没有在常量池创建。
StringBuilder 线程不安全
StringBuffer 线程安全每一个方法都用Synchornized修饰 并且这两个类创建的字符串都是可变的
普通就是遍历所有场景
增强for就是只读,但不需要指定下标
重载:同一个类参数不同
重写:不同类 参数和返回值相同 ; private 方法不能重写,权限不能变小
抽象类:单继承,有多种类型的成员变量,包括实例变量和静态变量,可以包含初始化块(静态代码块和非静态代码块),可以包含抽象方法和普通方法。
接口:多实现,一个类可以实现多个接口,不允许有构造方法和初始化块,成员默认是常量,方法默认public,jdk9之后允许与private方法。8之前接口中的所有方法默认是抽象的,8之后可以包含默认方法和静态方法。
设计意图:抽象类通常用来定义一组相关类的公共行为和状态,以及一些通用功能的实现。接口则更多地定义类的行为规范,而不关心具体的实现细节。它通常用来表达这个类能做什么。
封装:是指将数据和操作数据的方法捆绑在一起,也就是对象。封装还意味着隐藏对象的内部状态和实现细节,只暴露必要的公共接口供外界访问。这样做的好处是可以保护对象的状态不被外部代码随意更改,从而增加了代码的安全性和稳定性。
继承
多态s
public、protected、default
抽象类可以有静态方法和构造方法,但是不能被声明为abstract否则报错。
数组是一种用连续内存空间存储相同数据类型数据的线性数据结构。
寻址公式 a[i]=baseAddress+i*dataTypeSize
如果索引从1开始的话,对于cpu来说寻址增加了一个减法指令。每次寻址都增加一个指令,性能就会降低。
1.随机查询(根据索引查询)
常数级别
2.未知查询
如果顺序是混乱的就是线性级别。sorted就用二分查找,就是log级别
平均是线性级别的
1.数组是连续内存存储相同数据类型的结构
2.寻址的话直接用索引去找,指令简洁
3.时间复杂度问题
源码如何分析? 成员变量,构造函数,关键方法。
以java8为例
private static final int DEFAUlT_CATACITY =10;
//默认容量为10
private static final Object[] EMPTY_ELENMENTDATA={};
//对象数组 EMPTPY_EMLEMENTDATA
private static final Object[] DEFUALTCAPACITY_EMPTY_ELEMENTDATA={};
//另一个数组
transient Object [] elementData;
//
private size;
//长度
public ArrayList (int initialCapacity ){//容量
if(initialCapacity > 0){
this.elementData = new Object[initialCapacity];
}else if(initialCapacity==0){
this.elementData = EMPTY_ELEMENTDATA;
}else{
throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);//小于0抛出非法参数异常
}
}
public ArraryList(){
this.elementData = DEFUALTCAPACITY_EMPTY_ELEMENTDATA;
//默认容量空集合
}
public ArraryList(Collection<? extends E> c){
//将collection对象转换为数组,然后将数组的地址的值赋给elementdata
Object[] a = c.toArray();
if(size=a.length!=0){
if(c.getClass()==ArrayList.class){
elementData = a;
}else
elementData = Arrays.copyOf(a,size,Object[].class);
}else{
elementData = EMPTY_ELEMENTDATA;
}
}
public boolean add(E e){
ensureCapacityInternal(size+1);//确保内部容量
elementData[size++]=e;
return true;
}
首先我们使用无参构造器创建一个指定类型的默认数组ArrayList;
然后,进行add操作;
该语句只是声明和实例了一个ArrayList,容量为10,没有扩容。
1.数组转list,调用Arrays.asList方法转换成list
2.list转数组,用List toArray方法。无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组。
会受到影响。asList只是将list指向了数组地址。
底层使用Arrays类中的一个内部类ArrayList来构造的集合,在这个集合构造器中,把我们传入的集合进行了包装而已,最终指向的都是同一个内存地址。
不会受到影响。toArray中将list重新复制了一份给新的arr,是一个新的对象而非引用。
1.底层数据结构
2.操作数据效率
3.占用空间
4.线程安全
二叉树分类
比较常见的二叉树有:
满二叉树
完全二叉树
二叉搜索树(BST)
平衡二叉树
红黑树
B+树
B-树
概念:树中任意一个节点,左子树中的每个结点的值都要小于这个节点的值,右子树中节点的值都大于这个节点的值。
2^x = target ,x = log 级别时间复杂度
如果说这个二叉搜索树比较单一,退化成了链表,左右子树不平衡,此时查找时间复杂度就是On。
总结:
1.二叉树有两个子节点,左右子树都符合当前二叉树定义。
2.二叉搜索树,树中任意节点左右子树分别小于和大于当前节点的值。
概念:是一种平衡的二叉搜索树。
特质:
1.节点要么是红色要么是黑色
2.根节点是黑色
3.叶子节点都是黑色的空节点
4.红黑树中红色节点的子节点都是黑色
5.从任一节点到叶子结点的所有路径都包含相同数目的黑色节点
在添加或删除节点的时候,如果不符合这些性质会发生旋转,以达到所有的性质。
时间复杂度:O logn
红黑树也是一颗二叉搜索树,查找操作一样
有一个复杂度为常数级的旋转操作
红黑树的旋转规则
概念:散列表又名哈希表,是根据键直接访问内存存储位置值的结构,由数组演化而来,利用了数组支持下标随机访存数据的特性。
如何将Key 转换为 数组下标?
散列函数:将key映射为数组下标的函数。 可以表示为 hashvalue = hash(key)
要求:
散列冲突:
事实上,想找一个散列函数能够做到不同的keyhash出不同的结果几乎是不可能的,即使像著名的MD5,SHA等哈希算法也无法避免这一情况,这就是散列冲突(或者哈希冲突,哈希碰撞,就是指多个key映射到同一个下标位置)。
解决冲突办法:
1.拉链法:散列表中数组每个下标位置可以称为,桶或者槽,每个桶或者槽对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。
数据操作时间复杂度:
1.添加:O1 在链表里面是尾插
2.查找:平均情况下,常数级。 但是如果不理想,退化成了一个链表,就是On。这种情况是由于过多的冲突堆积到同一个桶里导致的。
这种情况下,可以把桶链表换成红黑树。也可以避免DDOS攻击,解决大量链表产生。
3.删除:一样
总结:
1.HashMap数据结构:底层使用hash表,数组+链表或者红黑树。
2.当我们往HashMap中put 元素时,利用key的hashCode, hash计算出当前对象在数组中的下标。
3.hashcode值相同怎么办?如果key相同,覆盖原始值,不同,放进桶里面。
4.桶里面,如果 linksize>8 && arr.length >64 转换为红黑树。
5.jdk 1.7和1.8 hashmap区别:
1.7:采用的拉链法解决hash冲突,只有链表。
1.8:增加了红黑树解决冲突堆积。扩容 resize()的时候,红黑树拆分的树节点 <=6 ,就退化成链表。
成员变量:
static final int DEFUALT_INITIAL_CAPACITY = 1 << 4;//乘16 默认初始
static final float DEFAULT_LOAD_FACTOR =0.75f; //默认的加载因子
//扩容阈值 = 数组容量 * 加载因子
transient HashMap.Node<K,V>[] table;
transient int size;
static class Node<K,V> implements Map.Entry<K,V>{
final int hash;
final K key;
V value;
HashMap.Node<K,V> next;
Node(int hash,K key,HashMap.Node<K,V> next)
this
this
this
this
}
构造函数:
//无参构造
public HashMap(){
this.loadFactor = DEFUAULT_LOAD_FACTOR
}
介绍:数组扩容时,因为是链表头插法,数据迁移的过程中,有可能导致死循环。
在数据迁移的过程中,没有新对象的产生。
是,但不是线程安全的。
Spring框架中的@Scope注解,默认的值就是singleton,单例的。
因为一般在spring的bean中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,时要考虑线程安全问题的,可以用多例或者加锁来解决。
那么多例或者加锁怎么弄呢?
AOP是面向切面编程的简称,具体是将那些与业务无关但是对多个对象产生影响的公共行为和逻辑,抽取封装成一个可重用的模块,这个模块就是切面,减少了重复代码。能够更好的维护和降低耦合。
使用场景:
记录操作日志
缓存处理
Spring内置事务处理
Spring支持编程式事务和声明式
@Component
@ComponentScan
@Controller
@RestController
@Service
@Repositroy
@Bean
@Configuration
@Scope
@Autowired
@Resource
@Qualifier
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeletMapping
@RequestParam
@PathVariable
@Requestbody
@ResponseBody
@Value
@ConfigurationProperties
@SpringBootApplication
@EnaleAutoConfiguration
@Transactional
@Scheduled
散列表解决冲突的方法主要分为两大类:开放定址法(Open Addressing)和链地址法(Separate Chaining)。下面详细介绍这两种方法及它们的变种:
开放定址法是在散列表中直接寻找下一个可用的位置来存储冲突的元素,直到找到一个空闲的位置。常用的技术包括:
线性探测(Linear Probing):当发生冲突时,顺序检查下一个槽位是否可用。例如,如果位置( h(k) )已被占用,尝试( h(k) + 1 ),如果仍然被占用,继续尝试( h(k) + 2 ),依此类推,直到找到一个空槽位。
二次探测(Quadratic Probing):使用一个二次函数来计算探测序列。例如,如果位置( h(k) )已被占用,尝试( h(k) + c_1^2 ),( h(k) + c_2^2 ),等等,其中( c_i )是从1开始递增的序列。
双重散列(Double Hashing):使用第二个散列函数( h_2(k) )来决定探测间隔。例如,如果位置( h_1(k) )已被占用,尝试( h_1(k) + i \cdot h_2(k) ),其中( i )从1开始递增。
链地址法是将所有散列到同一位置的元素存储在一个链表或其他数据结构(如平衡树)中。当冲突发生时,元素被添加到相应的链表中。这种方法的优点是处理冲突简单,不需要额外的探测策略。
除了上述两种主要方法外,还有其他的冲突解决策略,如:
再散列(Rehashing):使用第二个或更多的散列函数来重新计算位置,直到找到一个未被占用的位置。
公共溢出区(Coalesced Hashing):设置一个额外的溢出区域来存储那些无法在主散列表中找到位置的元素。
目录散列(Directory Hashing):创建一个目录来索引大的块,每个块内部使用自己的散列表。
动态散列(Dynamic Hashing):随着数据的增长,动态调整散列表的大小和散列函数。
选择哪种方法取决于具体的应用场景、预期的负载因子、内存限制和性能要求。在实际应用中,链地址法和某些形式的开放定址法是最常见的选择。例如,C++的STL中的std::unordered_map
和std::unordered_set
使用链地址法来解决冲突。
传统暴力算法思想:
1.M[0]指向主串头结点,S[0]指向待匹配子串头结点,对比copyM和copyS,匹配失败则M指向下一个字符。那么主串每一个字符开头的最长匹配串都会被扫描一遍,待匹配子串也会被遍历一边。最坏时间复杂度是 m*s;
2.为了优化空间资源的使用,我们不使用指针副本copyM去遍历主串,而是记录当前位置x,匹配失败后回到M[x+1]。
KMP算法思想:
当主串M[X]开头的最长匹配串s匹配失败的时候,我是否能够通过这个最长匹配串的信息,优化我下一次匹配?
已知,已匹配s是待匹配字符串S的一个前缀。
如果s[s.length]中存在相同的前后缀,那么此时指针M指向的是已匹配的后缀,也就是待匹配S的前缀,S中的前缀就不需要匹配,直接匹配M.next与S.next。此时,我S指针需要从字符串失败位(S[s.length])移动到S[S[s.length-1]记录的最长相同前后缀],也就是S[next[s.length-1]]的位置,主串也不需要回退。
关于主串指针不回退的解释:在KMP算法中,主串的指针实际上从未真正回退,这是该算法的一大优势。一旦发现不匹配,KMP算法是通过调整子串(模式串)的起始比较位置来继续搜索的,而不是让主串指针回退。这是因为通过分析子串内部的前后缀关系,我们已经知道在某个失配点之后,子串的前几个字符无需与主串重新比较——这部分匹配信息已经被“部分匹配表”所记录并利用。因此,主串指针保持在失配位置不变,等待子串调整到一个更合理的位置继续进行比较,这样就避免了重复比较,大大提高了效率。
在顺序队列操作中,假溢出的现象为:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间。所以A错。
在这种循环队列中,队列满时,front 与tail也是相等的。所以BC错。
堆必须是完全二叉树,分为大根堆,小根堆,顾名思义。但是从完全二叉树的编号排序,他们不一定是顺序的,所以有堆排序操作。
上滤:
下滤:
自上而下 Onlogn:我们将新元素插入到完全二叉树的最后一位,然后通过上滤建堆,是通过判断插入结点与根结点的大小,如果大的话就交换。
自下而上 On:我们将完全二叉树排列好,对每个小树进行下滤操作。
优先队列:使用小根堆实现,弹出堆顶元素后,将末尾元素(右子树最大元素)放到根节点,进行下滤。即可调整小根堆。Ologn
堆排序:不断执行类似优先队列的弹出操作。将子树末尾元素与根结点交换然后下滤。
二叉查找树中,任意一个树,其左孩子<根<右孩子。
查找:比根小,搜索左子树,比根大,搜索右子树。
插入:比根小,去左孩子,比根大,去右孩子
构建:顺序插入
删除:1.只有左子树 2.只有右子树 3.根节点(让直接前驱或者后继代替)
平衡二叉树首先是一个二叉搜索树,crud操作与二叉搜索树相同,但是每个结点包含平衡因子。对失衡结点有左旋和右旋的操作。失衡又有四种情况:1.LL 2.LR 3.RL 4.RR
平衡二叉树的结点计算:
P(N)=N层平衡二叉树的最少节点数
那么P(N)=P(N-1)+P(N-2)+1
Socket通信并不局限于TCP协议,也可以使用UDP协议进行通信。Socket是应用层与传输层的接口,它允许应用程序通过网络进行通信,无论是基于连接的(如TCP)还是无连接的(如UDP)。因此,根据具体的应用需求,可以选择TCP或UDP来实现Socket通信。
在TCP拥塞控制机制中,当拥塞窗口(cwnd)小于阈值(ssthresh,slow start threshold)时,拥塞窗口呈指数增长。这是因为在慢开始(Slow Start)阶段,每当发送方接收到一个ACK确认,它就会将拥塞窗口的大小增加一个MSS(Maximum Segment Size),这样cwnd就会随着每个RTT(Round Trip Time)成倍增长,直到cwnd达到ssthresh。
一旦拥塞窗口达到或超过ssthresh,算法将从慢开始切换到拥塞避免(Congestion Avoidance)阶段,在这个阶段,拥塞窗口的增长率会降低,通常情况下是每过一个RTT增加一个MSS,即呈线性增长。
如果网络中检测到拥塞,例如通过三个重复的ACK或超时事件,ssthresh会被降低,通常是设置为当前cwnd的一半,并且cwnd会被重置为1MSS(或慢开始阈值),然后重新开始慢开始过程。这种机制被称为快速重传(Fast Retransmit)和快速恢复(Fast Recovery)。
有十个,主要是InnoDB MyISAM 。InnoDB 支持事务外键 行级锁,提供高并发性能,适合高负载的OLTP应用。数据以聚集索引的方式存储,提高检索效率。
MyISAM 不支持事务外键,使用表级锁。适合读取多、更新少的场景,如数据仓库。具有较高的读性能和较快的表级锁定
聚簇的非叶节点存储的是索引值,叶节点存储的是完整的数据记录,一个表只能有一个聚簇索引,一般是表的主键,用于范围查询和排序。
非聚簇索引又称为辅助索引、二级索引,其非叶子节点存储的也是索引值,但是叶子节点存储的是数据行的主键和对应的索引列,一个表可以有多个非聚簇索引,主要用于快速定位要查找的列
物理存储:聚簇、非聚簇
数据结构:B+树、哈希、倒排、R-树、位图
字段:主键、唯一、普通、前缀
字段个数:单列、联合
1.查找性能高效
首先它是自平衡树,叶节点到根节点路径长度相同,B+树在插入和删除节点时会进行分裂和合并操作,以保持树的平衡,但它又会有一定的冗余节点,删除的时候树结构变化小,更高效。
查找,插入,删除等操作的时间复杂的是log的,能顾保证在大数据量的情况下也能有较快的响应时间。
2.高度增长较慢,查磁盘次数少
不像红黑树一样高度增长快,它是多叉树,非叶子节点保存主键或索引值和页面指针,使得每一页都能容纳更多的记录,因此内存中就能存放更多索引,容易命中缓存,使得查询磁盘的IO次数减少。
3范围查询能力强
叶子节点通过链表链接,从根节点定位到叶子节点查找到范围的起点后就能顺着链表找到后续数据。
最左匹配原则是指,在复合索引中,查询条件需要按照索引列的顺序从最左侧列开始依次匹配。只有查询条件中的列按照索引的最左边列开始进行匹配,索引才能被有效使用。
InnoDB中,B+树默认数据页是16KB,假设每个数据记录的主键和数据大小为1KB。每个非叶子节点存储的是指向子节点的指针和索引键。大概2000万条
使用二级非聚簇索引作为条件查询时,由于二级索引中只存储了索引字段的值和对应主键值,无法得到其它数据。如果要查询数据行中的其他数据,需要根据主键去聚簇索引查找实际的数据行。
不一定。例如查询条件中不包含索引列,低基数列索引效果不佳、或查询条件复杂且不匹配索引的顺序。对于一些小表,MySQL可能选择全表扫描而非使用索引,因为全表扫描的开销可能更小。
最终是否用上索引是根据MySQL成本计算决定的,评估CPU IO成本最终选择用辅助索引还是全表扫描。有时候确实是全表扫描成本低所以没用上索引。但有时候由于一些统计数据的不准确,导致成本计算误判,而没用上索引。
排查索引效果的方法:用EXPLIAN命令,在查询前加上EXPLAIN可以查看MySQL选择的执行计划,了解是否使用了索引,使用了哪些索引、估算的行数等信息。
主要观察EXPLAIN结果以下几点:
1.不能盲目建立索引
2.对于字段的值有大量重复的不要建立索引。比如性别,但是也不绝对。
3.对于一些长字段不应该建立索引。
4.当数据表的修改频率远大于查询时
5.频繁条件查询应该建立索引。如果是多个条件经常一起查询,则可以考虑联合索引,减少索引数量。
6.对于经常在order by 、 group by 、 distinct 后面的字段建立索引。这些操作通常需要对结果进行排序分组或者去重,而索引可以帮助加快这些操作的速度。
索引并不是越多越好。因为索引不论从时间还是空间上都是有一定成本的1)从时间上
每次对表中的数据进行增删改(INSERT、UPDATE 或 DELETE)的时候,索引也必须被更新,这会增加写入操作的开销。例如删除了一个 name 为面试鸭的记录,不仅主键索引上需要修改,如果 name字段有索引,那么 name 索引也需要修改,所以索引越多需要修改的地方也就越多,时间开销就大了,并且 B+ 树可能会有页分裂、合并等操作,时间开销就会更大。还有一点需要注意:MVSQL 有个查询优化器,它需要分析当前的查询,选择最优的计划,这过程就需要考虑选择哪个索引的查询成本低。如果索引过多,那么会导致优化器耗费更多的时间在选择上甚至可能因为数据的不准确而选择了次优的索引。
2)从空间上
每建立一个二级素引,都需要新建一个 B+ 树,默认每个数据页都是 16kb,如果数据量很大,系又很多,占用的空间可不小。
原则上命中索引,减少IO 比如回表
具体观察慢SQL,利用explain分析查询语句的执行计划,识别性能瓶颈,优化查询语句。
怎么优化查询语句?
合理设计索引,利用联合索引进行覆盖索引的优化,避免回表,减少一次查询和随机IO;
避免SELECT * ;
避免在SQL中进行函数计算等操作,使得无法命中索引;
避免使用%LIKE,导致全表扫描;
联合索引要满足最左匹配原则;
不要对无索引字段进行排序;
连表查询需要注意不同字段的字符集是否一致,否则也会导致全表扫描;
还有利用缓存和利用业务
1.数据从根节点找,根据比较数据键值与节点中存储的索引键值,确定数据落在哪个区间,从而确定分支,定位到叶子节点
2.叶子节点存储实际的数据行记录,一页默认16kb,存储的数据行不止一条
3.叶子节点中数据行以组的形式划分,利用页目录的结构,通过二分查找可以定位到对应的组
4.定位组以后,利用链表遍历就可以找到对应的数据行
首先count是用来统计行数的聚合函数。
功能上:
count(*):统计表中所有行的数量,包括null值。由于不对具体的列作处理,因此性能较高。
count(1):和count*几乎没有差别
count(字段名):统计指定字段不为null的行数。
效率上:count字段就是全表扫描,正常情况下它还需要判断字段是否是null值,因此理论上会比count1和count*慢。
但是如果字段不为null,例如是主键,那么理论上也差不多,而且本质上它们的统计功能不一样,在需要统计null的时候,只能用count1,count*,不需要统计null只能用count字段,所以也不用太纠结性能问题。
首先varchar不定长,char定长会自动填充。
虽然varchar不定长,但是存储数据时候会额外加1-2字节,字符长度超过255就用2字节。用于存储字符串长度信息。
理论上char会比varchar快,因为varchar长度不固定,处理需要多一次运算,但是实际上这种运算耗时微乎其微,而固定大小在很多场景下浪费空间,除非存储的字符确定是固定大小或者本身很短。不然都推荐使用varchar。
使用锁,redolog,undolog,mvcc实现。
1.锁:数据并发修改控制
2.redo log:记录事务对数据库的修改。在崩溃时回复未提交的数据。满足事务的持久性。允许系统恢复到最近的已提交状态。以写前日志WAL方式工作
3.undo log:记录事务的反向操作,保存数据的历史版本,用于事务回滚。在事务执行失败时恢复到之前的状态,保障数据的一致性和完整性。
4.mvcc:满足非锁定读的需求,提高并发读,实现读已提交,可重复读两种隔离级别。满足事务的隔离性。
并发控制机制,允许多个事务同时读取和写入数据库,而无需互相等待,从而提高数据库的并发性能。
在MVCC中,数据库为每个事务创建一个数据快照。每当数据被修改时,MySQL不会立即覆盖原有数据,而是生成新版本的记录。每个记录都保留了对应的版本号和时间戳。
多版本之间串联起来就形成了一条版本链,这样不同时刻启动的事务可以无锁地获得不同版本的数据(普通读)此时普通读写操作不会阻塞。写操作可以继续写,无非就是会创建新的数据版本(但只有事务提交后,新版本才会对其他事务可见。未提交的事务修改不会影响其他事务的读取),历史版本记录可供已经启动的事务读取。
可重复读 repeatable read ,为了兼容历史上statement格式的binlog,为了进一步分析binlog statement格式以及可重复级别的影响,需要先从主从复制开始。
主从复制:MySQL的定位就是提供一个稳定的关系型数据库,而为了解决单点故障带来的问题,需要采用主从复制的机制。所谓主从复制,其实就是通过搭建MySQL集群,集群中的机器分为主服务器和从服务器,主服务器提供写服务,从服务器提供读服务。
为了保证主从服务器之间的数据一致性,就需要数据同步,这个过程使用binlog进行的。
主服务器把数据便跟记录到binlog中,然后再把binlog传输给从服务器,从服务器恢复数据。
binlog又有3种格式:statement、row、mixed。statement表示记录sql原文,row就是行,表示记录具体的行更新细节。这意味着二进制日志中的每个条目都会详细列出发生变更的行的内容和修改。好处就是不会导致主从不一致,缺点就是记录内容更多。因为记录的内容更多,数据恢复需要更长的时间。mixed就是混合两种格式,MySQL会根据SQL的情况自己选择。 可重复读级别下两个都能生效,但是读已提交级别下,只有row生效。这也是为什么MySQL默认级别是RR了,为了兼容binlog的默认格式。
共享 排他(元数据 行级 表级 页级)
那么由于InnoDB加锁的数据级别是耦合的,加锁肯定会有冲突,比如说你去公共厕所关门如厕,结果公共厕所拆迁你还在拉。这个时候意向锁就出来了:
意向锁(表级锁分为共享和独占):用于表明该表中有什么锁,是一种标记
记录锁:顾名思义就是锁住当前记录,是作用到索引上的。如果记录没有索引去找聚簇索引会锁整个表。
间隙锁:用于应对记录可能不存在的情况。会锁住记录的间隙防止幻读的出现
临键锁:记录锁+间隙锁
插入意向锁
自增锁
阶段提交是mysql为了保证redo log和binlog一致性使用的一种机制。可以在崩溃恢复时保证数据的一致性分为两个阶段:准备阶段 +提交阶段
1.准备阶段:InnoDB引擎会先写redo log,并且将其标记为prepare阶段,表示事务已经准备提交但是还没有完成;2.提交阶段:当redo log变成prepare阶段之后,mysl server层会写binlog,binlog写完之后,会通知lnnoDB引擎,将redoloq标记成commit状态。表示事务完成。
redo log:记录修改了某个数据页的哪些位置。用于崩溃恢复。
binlog:记录的是sql语句的原始逻辑,例如如“给id=1这一行的age字段加1”,(statement格式的binlog,记录的就是sql语句)。主要用于主从
复制。
为什么需要二阶段提交:为了保证数据的一致性
阶段提交是如何保证崩溃恢复时的数据一致性:通过对比XID。
1.redo log处于prepare阶段,binlog还没写入,mysql挂了
事务还没有提交,binlog里面还没有,所以只要redolog的不做数,数据就是一致的
2.redolog处于prepare阶段,binlog写入了,mysql挂了
对比redo log和binlog是否一致,一致的话,则提交事务,不一致就回滚,也能保证数据一致。
如何对比redoloq和binlog:XID(事务ID)
扫描redo log,如果发现有prepare状态的redolog,则拿XID去binlog里面找,如果找到了具有相同XID的事务记录,说明数据已经保存好了,当前这个事务可以直接提交,否则回滚。
组提交:一种优化redo写入的技术。,一组事务的redo loq会全部先写入到redo log buffer中,然后一起刷盘到文件中,主要是减少io,提高并发性能。
死锁是指多个并发事务中,彼此之间出现互相等待的情况
如何解决:
1.mysql自己解决,有两种,一种是立即解决(当检测到死锁的时候,数据库自动回收其中的一个事务,以解决死锁),一种是延迟解决(设置等待超时时间,超过阈值就释放锁进行回滚)。
2.手动干预解决,通过命令手动快速找出阻塞的事务及其线程id,然后kill掉。
如何避免:
深度分页是指数据量很大的时候,按照分页访问后面的数据
1.子查询:
比如select * from mianshiya where name = ‘鱼皮’ limit 9999999,10;优化为:
select * from mianshiya where
name = 'yupi'
and id >=
(select id from mianshiya where name = 'yupi' order by id limit 99999990,1)
order by id limit 10;
name有索引的情况下,这样的查询能直接扫描name的二级索引,二级索引的数据量少,且在子查询中能直接得到id不需要回表。将子查询得到的id再去主键索引查询,速度很快,数据量很少。
如果直接扫描主键索引数据量较大
select * from mianshiya
inner join
(select id from mianshiya where name = 'yupi' order by id limit 99999990,10)
as mianshiya1 on mianshiya.id = mianshiya1.id
2.记录id
每次分页返回当前最大id,下次查询的时候带上这个id,就可以利用id > maxid过滤了,这种查询仅适合连续查询的情况,如果跳页就不生效了。
3.elasticsearch
可以考虑用搜索引擎来解决这个问题,不过es也有深度分页,不了解不要回答
主从同步机制是一种数据恢复技术,用于将主数据库上的数据同步到一个或多个从数据库中。
主要通过二进制日志 binlog 实现数据的复制。主数据库在执行写操作时候,会将这些操作记录到binlog中,然后推送给数据库,从数据库重放对应的日志即可完成复制。
MySQL主从复制类型:
异步复制是MySQL默认值,具体流程如下:
主库:
从库:
用一句话概括一下:主库提交事务会写binlog,会由一个dump线程监听binlog文件的变更,如果有更新会推送更新事件到从库,从库接收到事件后会拉取数据,会有一个IO线程将其写道relaylog中,慢慢消化,由SQL线程来重放数据。
异步复制有数据丢失风险,例如数据还未同步到从库,主库就给客户端响应,然后主库挂了,此时从库晋升主库的话数据是缺失的
半同步复制:
MySQL5.7之后搞了个半同步复制,有个参数可以选择成功同步几个从库就返回响应。
比如一共有3个从库,我参数配置1,那么只要有一个从库响应说复制成功了,主库就直接返回响应给客户端
并行复制:
以前从库是通过一个SQL线程按照顺序逐步执行主库的binlog日志指令。对于主库的高并发写入操作,这种串行执行的方式会导致从库复制速度跟不上主库,从而产生主从延迟问题。
所以MySQL引入了并行复制,说白了就是通过多个SQL线程来并发执行重放事件。MySQL提供了以下几种并行复制模式
MySQL5.6基于库级别的并行复制
假设主库有多个数据库,从库上,不同数据库的事务可以同时执行。即将不同数据库上的事务分配到不同的SQL线程中执行。如果主库大部分事务集中在一个数据库上,就没啥用了
MySQL5.7基于组提交Group Commit事务的并行复制
进一步细化了并行的颗粒度,从库级别细化到组提交级别。即MySQL会将组提交的事务视为彼此独立的事务,可以在从库并行重放。
如果主库开启了组提交,且事务之间没有冲突。那么这些事务都可以由多线程并行执行。
binlog中如果两个事务的last_committed相同,说明这两个事务是在同一个Group中提交内。但是粒度还是不够,如果大部分事务都集中在一个表上?
那么只有相同的lastcommitted可以并发,即使有些数据的更改和当前事务是不冲突的,也无法并发。
MySQL5.7基于逻辑时钟的并行复制
逻辑时钟是基于GroupCommit的并行复制,引入了时间标记的概念。
基于所有主库同时处于prepare阶段且未提交的事务不会存在锁冲突的情况(就是这些事务在从库执行的时候都可以并行执行),将这些事务都打上一个时间标记(实际实现用的是上一个提交事务sequence_number,即上面提到的binlog中的lastcommited)。
这样库在识别这些事务时,可以并行,进一步提高并发度。
所以提升主库的组提交事务可以让从库复制的并行度更高,所以MySQL5.7引入了两个参数:
MySQL8.0基于WriteSet的并行复制
在5.7中为了提升从库的事务回放速度,需要在主库中提高事务的并行度。主库上的事务越多线程并行提交,备库就能在更大程度上实现并行回放。然而,这种方式依赖于主库的并行提交情况,当主库事务是串行提交时,备库的回访效率会显著下降。
所以MySQL8.0引入了基于WriteSet的并行复制,即使主库上的事务是串行提交的,只要事务之间没有冲突,备库也可以并行回访这些事务,提升复制效率。
WriteSet是事务更新行的集合,通过哈希算法对主键或者唯一索引生成标识,记录在binlog中,即通过writeset判定事务之间的冲突,如果两个事务的writeset没有冲突,则它们可以并行回放。
首先我们要明确一点,延迟是必然存在的,无论怎么优化都无法避免延迟的存在,只能减少延迟的时间。
常见解决方式有以下几种:
除此之外也可以提一提配置问题,例如主库的配置高,从库的配置太低了,可以提高从库配置。如果面试官对MySQL比较熟,可能会追问一些偏DBA侧的问题,例如并行复制等。
并行复制其实也很好了解。就是为了提高从库binlog回放事务的速度,发展史从库级别到组提交到逻辑时钟再到现在的写集合。如果说回访的事务唯一索引没有哈希冲突,我就把事务放在不同的线程中去并行,这样就更快。
MySQL主从延迟的常见原因以及优化方案
原因 | 优化方案 |
---|---|
从库单线程复制 | 启用多线程复制:MySQL并行复制 |
网络延迟 | 优化网络连接,缩短主从之间的物理距离 |
从库性能不足 | 增加硬件资源 |
长事务 | 优化应用程序,减少主库写入压力,避免长事务。 |
从库太多 | 从库过多主库同步压力大,导致延迟,合理评估主从数量 |
从库负载过高 | 增加从库实例,分散查询压力,避免慢查询 |
再强调一下主从延迟是必然存在,无论怎么优化都无法避免延迟的存在,只能减少延迟的时间。例如即使启用多线程复制就一定可以避免延迟吗?所以业务上的解决方案还是以回答重点内的几个解决方式为准。
主要有3个原因,分别是存储方式、优秀的线程模型以及IO模型、高效的数据结构。
单线程设计的原因:
1.Redis操作是基于内存的,其大多数操作的性能瓶颈主要不是CPU导致的。
2.使用单线程模型,代码简便的同时也减少了线程上下文切换带来的性能开销。
3.Redis在单线程的情况下,使用IO多路复用模型就可以提高Redis的IO利用率了。
6.0引入多线程的原因:
随着数据规模的增长、请求量的增多,Redis的执行瓶颈主要在于网络IO。引入多线程处理可以提高网络IO处理速度。
跳表主要是通过多层链表来实现的,底层链表保存所有元素,而每一层链表都是下一层的子集。
扩展知识
接下来我们通过查询和添加元素来了解其功能流程:
1.查询元素:这里我们与传统的链表进行对比,来了解跳表查询的高效。
假设我们要查找50这个元素,如果通过传统链表的话(看最底层绿色的查询路线),需要查找4次,查找的时间复杂度为On。
但如果使用跳表的话,其实只需要从最上面的10开始,首先跳到40,发现目标元素比40大,然后对比后一个元素比70小。于是就前往下一层进行查找,然后40的下一个50刚好符合,返回就可以了。
跳表的平均时间复杂度是Ologn,最差的时间复杂度是On。
2.插入元素:我们插入一条score为48的数据
最终实现的效果如下如所示:
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传]
跳表中的实现解析:
Redis的跳表相对于普通的跳表多了一个回退指针,而且score可以重复。
typedef struct zskiplistNode{
//Zset 对象元素值
sds ele;
//元素权重值
double score;
//后退指针
struct zskiplistNode * backward;
//节点的level数组,保存每层上的前向指针和跨度
struct zskiplistLevel{
struct zskiplistNode *forward;
unsinged long span;
}level[];
}zakiplistNode;
补充插入的随机层级
#define ZSKIPLIST P8.25/*Skiplist P= 1/4 *
/* Returns a random level for the new skiplist node weThe return value of this function is between 1 and i不(both inclusive),with a powerlaw-alike distributior呆levels are less likely to be returned. */米int zslRandomLevel(void){
static const int threshold=ZSKIPLIST P*RAND MAX;int level =1;while(random()<threshold)level += 1;return(evel<ZSKIPLISTMAXLEVEL)?level:ZSKIPLI
通过调用zslRandomLevel方法来决定新插入的节点所在的层数 level。可以看到level从1开始,通过while循环,当产生的随机数小于Rnd——max的最大值的0.25倍时level+1。
Redis的hash是一种键值对集合,可以将多个字段和值存储在同一个键中,便于管理一些关联数据。
特点如下:
扩展知识
hash常用命令
HSET:设定hash中指定字段的值。
HGET:获取hash中指定字段的值。
HMSET:multi
HMGET:一次性获取多个字段的值
HDEL:删除hash中一个或者多个字段
HINCRBY:为hash中的字段加上一个整数值。
HSET user:1001 name "mianshiya"
HGET user:1001 name
HMSET user:1001 name "mianshiya" age "18" city "shanghai"
HMGET user:1001 name age
HDEL user:1001 age
HINCRBY user:1001 age 1
ZSet(有序集合,Sorted Set)是一种由跳表(Skip List)和哈希表(Hash Table)组成的数据结构。ZSet结合了集合Set的特性和排序功能,能够存储具有唯一性的成员,并根据成员的分数(score)进行排序。
ZSet实现由两个核心的数据结构组成:
当Zset元素数量较少的时,Redis会使用压缩列表(Zip List)来节省内存
缓存和数据库的同步可以通过以下几种方式:
以上就是实现数据库与缓存一致性的六种方式,这里前面三种都不太推荐使用,后面三种的话其主要根据实际场景:
缓存击穿:指的是某个热点数据在缓存中失效,导致大量请求直接访问数据库。此时,由于瞬间的高并发,可能导致数据库崩溃。
缓存穿透:指查询一个不存在的数据,缓存中没有相应的记录,每个请求都会区数据库查询,造成数据库负担加重。
缓存雪崩:之多个缓存数据在同一时间国企,导致大量请求同时访问数据库,从而造成数据库瞬间负载激增。
解决方案:
缓存击穿:
缓存穿透:
缓存雪崩:
SDS Simple Dynamic String 结构,并结合int、embstr、raw等不同的编码方式进行优化存储。
Redis中实现分布式锁的常见方法是通过set ex nx 命令+lua脚本组合使用。确保多个客户端不会获得同一个资源锁的同时,也保证了安全解锁和意外情况下锁的自动释放。
RedLock又称为红锁,是一种分布式锁的实现方案,旨在解决在分布式环境中使用Redis实现分布式锁时的安全性问题。
一般情况下,我们在生产环境中会使用主从+哨兵方式来部署Redis
如果我们正在使用Redis分布式锁,此时发生了主从切换,但从节点上不到一定已经同步了主节点的锁信息。
所以新的主节点上可能没有锁的信息。此时另一个业务去枷锁,一看所还没被占,于是抢到了锁开始执行业务逻辑。
此时就发生了两个竞争者同时进入临界区操作临界资源的情况,可能就会发生数据不一致的问题。
所以Redis官方推出了红锁。主要解决的问题就是当部分节点发生故障也不会影响锁的使用和数据问题的产生。
Redis持久化主要有四种方式:
首先,全用缓存就是不持久化,几乎不用。
AOF和RDB各有好坏,基本不单独使用。
用得比较多的还是混合持久化
Redis的主从复制是指一个Redis实例(主节点)可以将数据赋值到一个或多个从节点(从节点),从节点从主节点获取数据并保持同步。
常用的过期数据删除策略就两个:
定期删除对内存更加友好,惰性删除对CPU更加友好。 Redis采取的是定期删除+惰性删除
Redis中的热点Key问题是指某些Key被频繁访问,导致Redis的压力过大,进而影响整体性能甚至导致集群节点故障。
解决热点Key问题的主要方法包括:
Redis集群是通过多个Redis实例组成的,每个实例存储部分的数据(即每个实例之间的数据是不重复的)。
具体是采用哈希槽(Hash Slot)机制来分配数据,将整个键空间划分为16384个槽(slots)。每个Redis实例负责一定范围内的哈希槽,数据的Key经过哈希函数计算后对16384即可定位到对应的节点。
客户端在发送请求时,会通过集群的任意节点进行连接,如果该节点存储了对应的数据则直接返回,反之该节点会根据请求的键值计算哈希槽并路由到正确的节点。
简单来说,集群就是通过多台机器分担单台机器上的压力。
内存空间占用比较大的Key
解决:
开发方面:
对要存储的数据进行压缩,压缩之后再进行存储
大化小,即把大对象拆分为小对象,将一个大Key拆分为若干个小Key,降低单个Key的内存大小
使用合适的数据结构进行存储,比如一些用 String 存储的场景,可以考虑使用 Hash、Set 等结构进行优化
业务层面
可以根据实际情况,调整存储策略,只存一些必要的数据。比如用户的不常用信息(地址等)不存储,仅存储用户 ID、姓名、头像等。
优化业务逻辑,从根源上避免大 Key 的产生。比如一些可以不展示的信息,直接移除等
数据分布方面
采用 Redis 集群方式进行 Redis 的部署,然后将大 Key 拆分散落到不同的服务器上面,加快响应速度
命名规范简洁易读,可扩展,可复用,硬编码少。
工厂模式关注的是创建单一类型的对象,定义一个抽象方法,由子类实现具体对象的实例化。
抽象工厂模式关注的是创建一组相关的对象,提供一个接口来创建一组相关的或者互相依赖的对象,而无需指定他们的具体类。
TCP提供了可靠、面向连接的传输,使用于需要数据完整性和顺序的场景。
UDP提供了更轻量、面向报文的传输,适用于实时性要求高的场景。
总结:
特性 | TCP | UDP |
---|---|---|
连接方式 | 面向连接 | 无连接 |
可靠性 | 可靠, | 不可靠 |
流量控制/拥塞控制 | 提供 | 无 |
顺序保证 | 保证 | 不保证 |
头部大小 | 20字节以上 | 8字节 |
性能 | 低,延迟高 | 高,延迟小 |
数据传输模式 | 字节流传输 | 数据报传输 |
适用场景 | 文件传输、Web、邮件等 | 实时通讯、语音、视频、游戏等 |
客户端首先发送一个同步序列编号给服务器,服务器收到后回复同步序列确认消息,最后客户端再发送一个确认收到服务器确认消息。其实就是保证了客户端、服务器双方都有收发能力。然后客户端最后保证存活。
为什么要三次握手呢?
RFC 793明确指出了 三次握手的首要原因是:为了阻止历史的重复连接初始化导致的混乱
为什么三次握手就能解决这个问题呢?为什么不是两次或者四次。
因为网络情况复杂,发送方第一次发送请求后,可能由于网络原因阻塞,有可能再次发送。如果握手只有两次,接收方对发送方的请求只能同意或者拒绝,但无法识别当前请求是新的还是旧的
并且如果网络阻塞时间很长,发送方可能多次发送请求,且接收方可能全部接受这些连接,认为这些都是有效请求。就造成了资源浪费。
如果要避免这个情况,就必须让发送方去确认接收方的响应是不是响应对了。SYN-ACK 的ACK。
所以要三次就够了,四次多余。四次就是把服务器确认消息和发送同步序列好分开。
除了这个重复连接导致的混乱问题,还有一个同步序列号。
第一次挥手:FIN-ACK,客户端主动关闭连接,发送FIN包,进入finwati1状态。服务器收到FIN后,表示不再接收数据,但依旧可能发送数据
第二次挥手:服务器发送ACK包,确认已收到FIN。此时服务器进入close_wait状态,客户端进入fin wait 2.
第三次挥手:服务器完成所有数据传输后,发送fin 包,进入 last ack。客户端收到服务端fin包后,准备关闭连接。
第四次挥手:客户端发送最后一个ack,进入time wait 状态,等待可能迟到的fin包。服务器收到ack后关闭连接,进入close,客户端time wait 计时2msl 后 正式关闭连接
为什么要四次挥手?
主要是为了保证数据完整性。TCP是一个全双工协议,也就是双方都要关闭,每一方都向对方发送fin 和ack。
客户端发起连接断开,代表客户端数据传输完毕了,没有了。但是服务器可能还要有。
一定要四次挥手吗?
不一定,有时候可以变成三次。当服务器不需要发送数据的时候,直接将fin跟ack一起发回。
1)粘包与拆包(也称半包)现象:
粘包:指的是在 TCP传输中,发送方的多个数据包在接收方被合并成一个包接收,导致多条消息数据粘在一起,接收方无法正确区分这些消息的边界拆包:指的是发送方的一个数据包在接收方被分成了多个包接收,导致一条完整的消息被拆成多个部分,接收方无法一次性接收到完整的数据。
2)原因:
,粘包:主要中于 TCP 是面向字节流的协议,它不关心数据边界,数据在发送方可能被一次性发送,接收方在读取时可能会将多个消息拼接在一起拆包:可能由于网络传输中的 MTU(最大传输单元)限制或发送缓冲区大小限制,一个大包被分成了多个小包传输。
3)解决方法:
使用定长消息:每个消息都有固定的长度,接收方按照固定长度读取数据。
添加消息分隔符:在每个消息之间添加特定的分隔符(如换行符),接收方可以通过分隔符来区分消息。
使用消息头:在消息的头部添加一个长度字段,指示消息的长度,接收方根据这个长度来读取相应长度的数据。
慢启动、拥塞避免、快速重传、快速恢复
应用层、传输层、网络层、网络接口层。
Cookie是明文存储的某网站本地信息文件用于维护用户状态。
Session是将用户状态信息存储到服务端,有本地存储但是负载均衡后就没了,session同步有额外开销,所以用第三方存储如Redis。
Token则是将签名附在密钥后面给客户,客户带着这个能认证各个平台。
上述都是从认证的角度去看的。其实这几个东西都很有用。
进程:是资源分配的基本单位,每个进程都有自己独立的内存空间(代码段、数据段、堆栈等),可以看作是一个正在运行的程序实例。进程之间是相互独
立的。
一个进程中可以包含多个线程。线程共享进程的内存空间和资源(如文件句柄、数据段),但每个线程有自己线程:是 CPU 调度的基本单位,属于进程,独立的栈和寄存器。
其它区别:
资源消耗不同:进程时需要为其分配独立的内存空间和系统资源,创建和切换进程的开销较大。线程间共享进程的资源,创建线程所需的开错较小,线程切换的开销也远小于进程切换。
通信方式:因为各自独立的内存空间,进程间通信(IPC)较为复杂,需要使用管道、消息队列、共享内存、套接字等方式。同一进程内的线程共享内存空间,因此线程直接读写内存即可,但注意需要使用同步机制避免数据错误。
管道,命名管道,消息队列,共享内存,信号量,信号,套接字,文件。
先来先服务(FCFS,First-Come,First-Served):按照进程到达的顺序进行调度,适用于批处理系统。简单易实现,但可能造成“长任务”拖延其他
任务的执行。
短作业优先(SJf,shortest Job First):优先调度执行时间最短的进程,能咸少平均等待时间。分为非抢占式和抢占式(SRTF,Shortest RemainincTime First)。但它需要预先知道任务执行时间,不适用于交互式系统。
优先级调度(Priority Scheduling):根据进程的重要性(优先级)来调度,优先级高的进程先执行。适用于需要不同优先级服务的场景。可能导致“低优先级进程”长期得不到调度,造成饥饿现象。
时间片轮转(RR,Round Robin):为每个进程分配固定的时间片,时间片结束后调度下一个讲程。适用于交互式系统,能提升系统响应性。时间片的
选择对系统性能有重要影响。
最高响应比优先(HRRN, Highest Response Ratio Next):通过计算响应比来决定下一个被调度的讲程,适合在批处理环境中平衡长短任务的等待时间,防止短任务过多导致长任务饥饿。
多级反馈队列调度(MLFO,MultilevelFeedbackOueue):结合多个调度策略,通过将进程放入不同优先级的队列,实现灵活的调度机制,优先级较高的进程先被调度,随着执行时间增加,进程可能被降至低优先级队列。适合多任务、多类型的操作系统。
Invalid bound statement (not found): com.fqxiny.mapper.EmployeeMapper.selectEmpsById
据包在接收方被分成了多个包接收,导致一条完整的消息被拆成多个部分,接收方无法一次性接收到完整的数据。
2)原因:
,粘包:主要中于 TCP 是面向字节流的协议,它不关心数据边界,数据在发送方可能被一次性发送,接收方在读取时可能会将多个消息拼接在一起拆包:可能由于网络传输中的 MTU(最大传输单元)限制或发送缓冲区大小限制,一个大包被分成了多个小包传输。
3)解决方法:
使用定长消息:每个消息都有固定的长度,接收方按照固定长度读取数据。
添加消息分隔符:在每个消息之间添加特定的分隔符(如换行符),接收方可以通过分隔符来区分消息。
使用消息头:在消息的头部添加一个长度字段,指示消息的长度,接收方根据这个长度来读取相应长度的数据。
慢启动、拥塞避免、快速重传、快速恢复
应用层、传输层、网络层、网络接口层。
Cookie是明文存储的某网站本地信息文件用于维护用户状态。
Session是将用户状态信息存储到服务端,有本地存储但是负载均衡后就没了,session同步有额外开销,所以用第三方存储如Redis。
Token则是将签名附在密钥后面给客户,客户带着这个能认证各个平台。
上述都是从认证的角度去看的。其实这几个东西都很有用。
进程:是资源分配的基本单位,每个进程都有自己独立的内存空间(代码段、数据段、堆栈等),可以看作是一个正在运行的程序实例。进程之间是相互独
立的。
,一个进程中可以包含多个线程。线程共享进程的内存空间和资源(如文件句柄、数据段),但每个线程有自己线程:是 CPU 调度的基本单位,属于进程,独立的栈和寄存器。
其它区别:
资源消耗不同:进程时需要为其分配独立的内存空间和系统资源,创建和切换进程的开销较大。线程间共享进程的资源,创建线程所需的开错较小,线程切换的开销也远小于进程切换。
通信方式:因为各自独立的内存空间,进程间通信(IPC)较为复杂,需要使用管道、消息队列、共享内存、套接字等方式。同一进程内的线程共享内存空间,因此线程直接读写内存即可,但注意需要使用同步机制避免数据错误。
管道,命名管道,消息队列,共享内存,信号量,信号,套接字,文件。
先来先服务(FCFS,First-Come,First-Served):按照进程到达的顺序进行调度,适用于批处理系统。简单易实现,但可能造成“长任务”拖延其他
任务的执行。
短作业优先(SJf,shortest Job First):优先调度执行时间最短的进程,能咸少平均等待时间。分为非抢占式和抢占式(SRTF,Shortest RemainincTime First)。但它需要预先知道任务执行时间,不适用于交互式系统。
优先级调度(Priority Scheduling):根据进程的重要性(优先级)来调度,优先级高的进程先执行。适用于需要不同优先级服务的场景。可能导致“低优先级进程”长期得不到调度,造成饥饿现象。
时间片轮转(RR,Round Robin):为每个进程分配固定的时间片,时间片结束后调度下一个讲程。适用于交互式系统,能提升系统响应性。时间片的
选择对系统性能有重要影响。
最高响应比优先(HRRN, Highest Response Ratio Next):通过计算响应比来决定下一个被调度的讲程,适合在批处理环境中平衡长短任务的等待时间,防止短任务过多导致长任务饥饿。
多级反馈队列调度(MLFO,MultilevelFeedbackOueue):结合多个调度策略,通过将进程放入不同优先级的队列,实现灵活的调度机制,优先级较高的进程先被调度,随着执行时间增加,进程可能被降至低优先级队列。适合多任务、多类型的操作系统。
Invalid bound statement (not found): com.fqxiny.mapper.EmployeeMapper.selectEmpsById