1.什么是乐观锁?
乐观锁就是总是认为不会产生并发的问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般使用版本号机制或CAS机制来实现
2.CAS机制
CAS是乐观锁的一种实现方式,是一种轻量级的锁
原理:线程在读取数据时不进行加锁,在准备写回数据时,先去查询原值是否被修改,若被修改则写回,否则,重新执行读取流程
3.乐观锁带来的问题
CAS操作长时间不成功,会导致一直自旋,相当于死循环,造成CPU消耗过大
4.什么是悲观锁?
悲观锁总是假设最坏情况,每次取数据时都认为其他线程会修改数据,所以都会加锁,当其他线程想要访问数据时,都要挂起等待
5.乐观锁和悲观锁的适用场景
①响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就不执行,不需要等待其他并发去释放锁
②冲突频率:如果冲突频率非常高,建议采用悲观锁来保证成功率,因为冲突频率大的话,乐观锁需要多次重试才能成功,代价比较大
③重试代价:如果重试代价大,建议采用悲观锁
6.哈希函数也叫散列函数,哈希表也叫散列表
7.什么是哈希冲突?
不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”
8.如何解决哈希冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
1.闭散列
闭散列:也叫开放定址法(开地址法),当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。如何寻找下一个空位置成为了解决问题的关键所在!
线性探测
现在需要在一个位置插入元素44,先通过哈希函数计算哈希地址,该位置的hashAddr为4,因此44这个位置理论上应该插在该位置,但是该位置已经放了值为4的元素,即发生哈希冲突。
线性探测:从发生哈希冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
线性探测优点
实现非常简单
线性探测缺点
一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低
插入
通过哈希函数获取待插入元素在哈希表中的位置
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素
删除
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。比如删除元素4,如果直接删除掉,44查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。
二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法为: = ( + )% m,或者: = ( - )% m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小
闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷!
2.开散列
开散列概念
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中
开散列增容
桶的个数是一定的,随着元素的不断插入,每个桶中元素的个数不断增多,极端情况下,可能会导致一个桶中链表节点非常多,会影响的哈希表的性能,因此在一定条件下需要对哈希表进行增容,那该条件怎么确认呢?开散列最好的情况是:每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突,因此,在元素个数刚好等于桶的个数时,可以给哈希表增容!
3.开散列与闭散列比较
应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间