更新时间:2023年09月25日15时41分 来源:传智教育 浏览次数:
在多线程开发中,对共享资源的争抢过程(Data Race)就是并发,而对共享资源数据进行访问保护的最直接办法就是引入锁。锁的作用是解决在并发状态下的共享资源互斥问题,保证在同一时间只有一个进程/线程可以掌握资源的控制权。
单机环境下,资源竞争者都是来自机器内部((进程/线程),那么实现锁的方案只需要借助单机资源就可以了。
Synchronized中的重量级锁,底层就是基于锁监视器(Monitor)来实现的。简单来说就是锁对象头会指向一个锁监视器,而在监视器中则会记录一些信息,比如:
- _owner:持有锁的线程
- _recursions:锁重入次数
因此每一个锁对象,都会指向一个锁监视器,而每一个锁监视器,同一时刻只能被一个线程持有,这样就实现了互斥效果。但前提是,多个线程使用的是同一把锁。
比如有三个线程来争抢锁资源,线程1获取锁成功,如图所示:
此时其它线程想要获取锁,会发现监视器中的_owner已经有值了,就会获取锁失败。
分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
分布式环境下会出现资源竞争的地方都需要分布式锁的协调
分布式锁的作用:在整个系统提供一个全局、唯一的锁,在分布式系统中每个系统在进行相关操作的时候需要获取到该锁,才能执行相应操作。
由于我们的服务将来肯定会以多实例形式部署,形成集群。每一个实例都会有一个自己的JVM运行环境,因此即便是同一个用户,如果并发的发起了多个请求,由于请求进入了多个JVM,就会出现多个锁对象(用户id对象),自然就有多个锁监视器。此时就会出现每个JVM内部都有一个线程获取锁成功的情况,没有达到互斥的效果,并发安全问题就可能再次发生了:
可见,在集群环境下,JVM提供的传统锁机制就不再安全了。
那么该如何解决这个问题呢?
显然,我们不能让每个实例去使用各自的JVM内部锁监视器,而是应该在多个实例外部寻找一个锁监视器,多个实例争抢同一把锁。像这样的锁,就称为分布式锁。
分布式锁的实现方案
- 基于数据库表
操作数据库需要较大的开销,性能差
- 基于缓存实现分布式锁(redis)
- 基于Zookeeper实现分布式锁
- 基于consul实现分布式锁
- 基于etcd实现分布式锁