关于对"锁"使用的爱与恨

为什么要使用锁

锁(Locks)是并发编程中一个非常重要的概念,它主要用于解决多线程环境下的数据访问冲突问题。使用锁的主要原因如下:

  1. 数据一致性:在多线程环境中,如果多个线程同时访问并修改共享数据,可能会导致数据不一致。使用锁可以确保在任何时刻只有一个线程能够访问和修改共享数据。
  2. 避免竞态条件:竞态条件(Race Condition)是指在多线程程序中,由于线程的执行顺序不确定,导致程序的行为不可预测。锁可以防止这种情况的发生。
  3. 实现线程同步:锁提供了一种机制,允许线程之间进行同步。一个线程可能需要等待另一个线程完成某些操作后才能继续执行。
  4. 保护临界区:临界区(Critical Section)是一段代码,其中包含对共享资源的访问。使用锁可以确保在任何时刻只有一个线程能够执行临界区的代码。
  5. 简化编程模型: 锁提供了一种相对简单的方式来处理并发问题,使得开发者可以更容易地编写并发程序。

锁的常用类型

锁在并发编程中有多种类型,每种锁都有其特定的使用场景和特点。以下是一些常见的锁类型及其使用场景:

  1. 互斥锁(Mutex)
    • 作用: 确保同一时间只有一个线程可以访问共享资源。
    • 使用场景: 保护共享数据,防止多个线程同时修改。
  2. 读写锁(Reader-Writer Lock)
    • 作用: 允许多个读操作同时进行,但写操作是互斥的。
    • 使用场景: 当读操作远多于写操作时,可以提高性能。
  3. 自旋锁(Spinlock)
    • 作用: 在尝试获取锁时不会立即阻塞,而是循环检查锁是否可用。
    • 使用场景: 在锁持有时间短且线程不希望在等待时放弃CPU时间的情况下。
  4. 乐观锁
    • 作用: 通过数据版本控制(如CAS操作)来实现,不实际锁定数据,而是在更新时检查数据是否被其他线程修改。
    • 使用场景: 适用于冲突较少的场景,可以减少锁的开销。
  5. 悲观锁
    • 作用: 假设会发生冲突,因此在访问数据时直接加锁。
    • 使用场景: 在高冲突环境中,确保数据一致性。
  6. 分布式锁
    • 作用: 在分布式系统中,用于确保跨多个节点的资源访问一致性。
    • 使用场景: 在分布式数据库或缓存中同步数据。

实际案例

在网约车派单场景下,最近团队接到一个需求,要能够支持一个订单可以派送两个司机。每轮派单时,先派送一个司机;派送完第一个司机后,可以继续支持派送第二个司机。订单派送第一个司机和第二个司机的策略不同,需要进行区分。

开发人员的解决方案:

  • 在订单上增加一个策略标识,订单派送第一个司机时标识为A策略,再派送第二个司机时标识为B策略。

现在面临并发冲突的场景是:

  1. 订单已经派送完了第一个司机,但是由于司机拒绝接单,第一次派单结果需要废弃,重新进行派单处理。
  2. 订单第一次派单结果废弃通知时,线程1来处理;此时订单正好派送第二个司机,线程2来处理。

开发人员的解决方案:

  • 由于第一次派单结果废弃经过线程1处理会影响订单的派单策略;订单第二次派送完成经过线程2处理也会影响订单的派单策略。当两个独立的线程需要对同一资源进行修改时,此时就需要加锁 控制,来避免出现竞态条件
  • 上面的锁是互斥锁:当一个线程占用锁时,另一个线程再来抢占锁,直接提示抢占失败,防止出现等待时间过长,影响接口的性能。

上面的方案以及并发冲突进行的加锁控制,从表面上来看好像都是正常逻辑,没有什么问题。但是我们仔细一琢磨,就会发现不对劲,第一个司机拒绝订单导致的结果,和订单派送第二个司机,业务上理解并没有出现共享数据的问题,为什么需要锁来解决?

原来一开始的解决方案设计就出现了问题,不能针对订单增加标识来解决。针对同一个订单,由于需要派送两个司机,当两个司机并发处理操作时,就必然对订单这个共享资源产生了并发竞争问题。

解决方案:

  • 订单派送完第一个司机A后,不再增加标识,而是新增一个订单和司机A的关系;后面订单再派送第二个司机B时,和第一个司机A对订单的操作,从资源上就隔离了。派单时,基于订单和司机的关系统计派送的司机数,来决定使用什么策略。
  • 针对同一个订单,A司机和B司机的操作,不再影响派单策略;同时,由于不加锁,也进一步提升了接口的性能。

总结

在我们的日常工作中,对加锁这个词要提高敏感度,尽量避免加锁引起的复杂度和低效问题。对每个加锁场景仔细确认清楚下面三个问题:

  1. 共享的资源,是否设计合理,有没有优化的空间。
  2. 多线程竞争资源,能否避免,使用单线程来解决。
  3. 能否使用CAS的原子性操作来避免锁的问题,同时又不增加过多的复杂度。

如果以上三个场景,确实都无法覆盖,再考虑使用哪种类型的锁,来解决不同场景的问题。

相关推荐
用户3157476081351 小时前
成为程序员的必经之路” Git “,你学会了吗?
面试·github·全栈
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
布川ku子2 小时前
[2024最新] java八股文实用版(附带原理)---Mysql篇
java·mysql·面试
背水2 小时前
初识Spring
java·后端·spring
晴天飛 雪2 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590452 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端
AskHarries3 小时前
Spring Cloud Gateway快速入门Demo
java·后端·spring cloud
Qi妙代码3 小时前
MyBatisPlus(Spring Boot版)的基本使用
java·spring boot·后端