单个jvm的线程安全问题可以用lock锁或者synchronize解决,但是如何解决多个jvm的线程安全问题,这就要借助于分布式锁,分布式锁产生的原因是集群,主要场景:生成分布式全局订单号id。
在集群的情况下,多台机器需要共享一个全局count,这就需要分布式锁:
分布式锁常用的实现:
- 数据库行锁(效率低,不推荐)
- redis的redission(需要考虑思索,释放问题。繁琐一些)
- Zookeeper实现 (使用临时节点,效率高,失效时间可以控制)
zk实现分布式锁的原理
使用zookeeper创建临时序列节点来实现分布式锁,思路就是创建临时序列节点,获取分布式锁,在未释放之前,其他应用不能创建相同路径节点,程序执行完成之后此临时节点消失,通过watch来监控节点的变化,其他应用可以重新获取,以此类推。。。
实现步骤
多个Jvm同时在Zookeeper上创建同一个相同的节点( /Lock),zk节点唯一的!节点类型为临时节点, jvm1创建成功时候,jvm2和jvm3创建节点时候会报错,该节点已经存在。这时候 jvm2和jvm3进行等待。 jvm1的程序现在执行完毕,执行释放锁。关闭当前会话,临时节点不复存在了并且事件通知Watcher,jvm2和jvm3继续创建。
代码实现
创建锁接口
package com.toov5.Lock;public interface ExtLock { //ExtLock基于zk实现分布式锁 public void getLock(); //释放锁 public void unLock(); }
模板方法
import org.I0Itec.zkclient.ZkClient;//将重复代码抽象到子类中(模板方法设计模式)public abstract class ZookeeperAbstractLock implements ExtLock { private static final String CONNECTION="192.168.91.5:2181"; protected ZkClient zkClient = new ZkClient(CONNECTION); private String lockPath="/lockPath"; //获取锁 public void getLock() { //1、连接zkClient 创建一个/lock的临时节点 // 2、 如果节点创建成果,直接执行业务逻辑,如果节点创建失败,进行等待 if (tryLock()) { System.out.println("#####成功获取锁######"); }else { //进行等待 waitLock(); } //3、使用事件通知监听该节点是否被删除,如果是,重新进入获取锁的资源 } //创建失败 进行等待 abstract void waitLock(); abstract boolean tryLock(); //释放锁 public void unLock() { //执行完毕 直接连接 if (zkClient != null) { zkClient.close(); System.out.println("######释放锁完毕######"); } } }
接口方法实现类
package com.toov5.Lock;import org.I0Itec.zkclient.ZkClient;//将重复代码抽象到子类中(模板方法设计模式)public abstract class ZookeeperAbstractLock implements ExtLock { private static final String CONNECTION="192.168.91.5:2181"; protected ZkClient zkClient = new ZkClient(CONNECTION); private String lockPath="/lockPath"; //获取锁 public void getLock() { //1、连接zkClient 创建一个/lock的临时节点 // 2、 如果节点创建成果,直接执行业务逻辑,如果节点创建失败,进行等待 if (tryLock()) { System.out.println("#####成功获取锁######"); }else { //进行等待 waitLock(); } //3、使用事件通知监听该节点是否被删除 ,如果是,重新进入获取锁的资源 } //创建失败 进行等待 abstract void waitLock(); abstract boolean tryLock(); //释放锁 public void unLock() { //执行完毕 直接连接 if (zkClient != null) { zkClient.close(); System.out.println("######释放锁完毕######"); } } }