0%

为什么并发执行线程要加锁

保证共享资源访问的安全性,避免出现竞态条件

竞态条件:多个线程同时访问和修改共享资源时,由于线程调度的不确定性,会出现多个线程交叉执行的情况

通过加锁,确保任何时刻只有一个线程访问共享数据,从而避免竞态条件,确保数据的一致性和完整性。

自旋锁是什么?

特点:当线程尝试获取锁但是锁已经被占用时,不会立即阻塞,而是通过循环重试(忙等)的方式来持续检查锁的状态,直到获取锁为止。

自旋锁通过CPU提供的CAS函数,在用户态完成加锁和解锁操作,不会主动产生线程上下文切换,因此开销更小,自旋锁会占用CPU资源,因此适合锁被占用时间极短的场景

一般加锁的过程:

  1. 查看锁的状态,如果锁是空闲的,执行第二步
  2. 将锁设置为当前线程所有

CAS合并这两步形成原子指令

注意:单核CPU需要抢占式调度器,否则自旋锁不能使用,因为自旋锁不会放弃CPU的控制权

死锁发生的条件

死锁:多个线程互相等待对方所持有的资源,且每个进程都无法主动释放自己已占有的资源时,导致线程的永久阻塞。

  1. 互斥条件:多个线程同时使用同一个资源
  2. 持有并等待: 线程已持有部分资源,又去请求其他资源,请求失败也不去释放已有资源而是继续持有并等待
  3. 不可剥夺条件:线程的资源不能被强制夺取,只能由持有者自己释放
  4. 环路等待条件:存在线程-资源的循环等待链,每个进程都在等待链中下一个进程锁持有的资源,形成闭环。

如何避免死锁

破坏死锁的任意一个条件即可,最常用且可行的是使用资源有序分配法,破坏环路等待条件

资源有序分配法:线程A和线程B所需的资源的顺序一样,当线程A尝试先获取资源A,然后获取资源B,线程B也是先获取资源A再获取资源B。也就是说,线程A和线程B总是以相同的顺序获取自己需要的资源。

银行家算法

银行家算法是一种用来避免死锁的资源分配策略。

在给进程分配资源前,先判断进程的安全性,也就是预执行判断分配后是否存在死锁等现象。如果当前资源能满足其执行,则尝试分配,如果不满足,则让进程等待

通过不断检查剩余可用资源,看是否满足某个进程的最大需求,如果可以则加入安全序列,并把该进程当前持有的资源回收;不断重复这个过程,看最后能否把所有进程都添加进安全序列

安全序列一定不会发生死锁,但没有死锁不一定是安全序列

银行家算法缺点:

  1. 进程提前声明最大资源需求”,实际难满足
  2. 不适用于资源 / 进程动态变化的系统
  3. 安全性检查时间复杂度高,开销大

乐观锁和悲观锁的区别

乐观锁:

  • 基本思想:乐观锁假设多个事务之间很少发生冲突,因此读取数据时不会加锁,而是在更新数据时检查数据的版本(时间辍或版本号),如果版本匹配则执行更新操作,否则认为发生了冲突
  • 使用场景:乐观锁适合读多写少的场景,可以减少锁的竞争,提高并发性能。如数据库中的乐观锁可以用于处理并发更新同一行的场景。

悲观锁:

  • 基本思想:假设多个事务之间频繁发生冲突,读取数据时会加锁,防止其他事物对数据进行修改,直到事务完成后释放锁
  • 使用场景:适合写多的场景,通过加锁保证数据的一致性。如数据库的行级锁机制用来处理并发更新同一行的情况。

乐观锁适用于读多写少的场景,使用版本控制避免冲突;悲观锁适用于写多的场景,通过加锁避免冲突。