1. 为什么要使用分布式锁?
- 变量A存在JVM1、JVM2、JVM3三个JVM(进程)内存中
- 变量A同时都会在JVM分配一块内存,三个请求发过来同时对这个变量进行操作(例如3个人买书,书的库存只有2本),显然结果是不对的
- 不是同时发过来,三个请求分别操作三个不同JVM内存区域的数据,变量A之间不存在共享,也不具有可见性,处理的结果也是不对的
注:这个变量主要体现在一个类中有一个成员变量,它是一个有状态的对象
2. 分布式锁应该具备哪些条件?
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行
- 高可用的获取与释放锁(zookeeper一直都能提供锁,并且释放锁)
- 高性能的获取锁与释放锁(快)
- 具备可重入性特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。
可重入函数:
1.不为连续的调用持有静态数据。
2.不返回指向静态数据的指针;所有数据都由函数的调用者提供。
3.使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4.如果必须访问全局变量,记住利用互斥信号量来保护全局变量。
5.绝不调用任何不可重入函数。
- 具备锁失效机制,防止死锁
回顾:多线程是如何避免死锁的?
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
1.破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
2.破坏请求与保持条件
一次性申请所有的资源。
3.破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
4.破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
我们对线程 2 的代码修改成下面这样就不会产生死锁了
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
说明:类似于熔断机制,避免大量申请锁等待而导致的阻塞.
什么是Zookeeper
Zookeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一的文件名,即同一目录下文件名不能重复
Zookeeper实现分布式锁的步骤
- 创建一个目录MyZookeeper
- 线程A想要获取锁就在MyZookeeper目录下创建一个临时顺序节点
- 线程A获取MyZookeeper目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁
- 此时线程B也获取锁,它会先获取所有的MyZookeeper节点下的子节点,如果判断自己不是最小的节点,就设置监听比自己次小的节点
- 线程A处理完毕,删除自己的节点,线程B监听到变更事件(加入线程B次小的节点是A),还是判断下自己是不是最小的节点,如果是就获得锁
参考:单点故障与分布式锁