前言:
jdk提供的synchronized和ReentrantLock可以帮助我们在单进程中解决资源共享数据一致性,但是在分布式系统中是多进程多线程,这个时候仅仅使用jdk实现的锁解决不了资源共享的问题,比如某商城中数据库有10个商品,A用户想要买走6个,B用户想买走5个。如果系统运行在单台机器上,我们使用Jdk提供的锁,可以保证数据的一致性,但是当系统运行在多台机器中,JDK实现的锁就会失效,这个时候就应该使用分布式锁,每次只能保证一台机器在请求资源。分布式锁有三种不同的方式实现,分别是数据库提供的分布式锁、redis、zookeeper实现,今天主要讲zookeeper实现分布式锁
在学习zk实现分布所之前,我们应该需要了解一些zk的知识
1、持久节点:客户端断开连接zk不删除persistent类型节点
2、临时节点:客户端断开连接zk删除ephemeral类型节点
3、顺序节点:节点后面会自动生成类似0000001的数字表示顺序
4、节点变化的通知:客户端注册了监听节点变化的时候,会调用回调方法
源码地址:githut源码地址
一、zk实现的简单的分布式锁
1、zk实现简单的分布式锁的思路,主要是抓住一下三点
(1)、当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)
(2)、我们注册该节点的监听时间,当节点删除,会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为时拿到锁的客户端释放锁,其他的客户端可以抢锁)
(3)、创建的节点应该时临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁
2、图解
![](https://img.kancloud.cn/09/c0/09c023ef0efd2d4d4510c97dfb272a56_513x474.png)
3、代码实现
AbstractLock.java
```
package zklock;
import org.I0Itec.zkclient.ZkClient;
public abstract class AbstractLock {
//zk地址和端口
public static final String ZK_ADDR = "192.168.0.230:2181";
//超时时间
public static final int SESSION_TIMEOUT = 10000;
//创建zk
protected ZkClient zkClient = new ZkClient(ZK_ADDR, SESSION_TIMEOUT);
/**
* 可以认为是模板模式,两个子类分别实现它的抽象方法
* 1,简单的分布式锁
* 2,高性能分布式锁
*/
public void getLock() {
String threadName = Thread.currentThread().getName();
if (tryLock()) {
System.out.println(threadName+"-获取锁成功");
}else {
System.out.println(threadName+"-获取锁失败,进行等待...");
waitLock();
//递归重新获取锁
getLock();
}
}
public abstract void releaseLock();
public abstract boolean tryLock();
public abstract void waitLock();
}
```
AbstractLock类是个抽象类,里面getLock使用模板模式,子类分别是简单的zk锁和高性能的zk锁
SimpleZkLock.java
```
package zklock;
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;
/**
* @author hongtaolong
* 简单的分布式锁的实现
*/
public class SimpleZkLock extends AbstractLock {
private static final String NODE_NAME = "/test_simple_lock";
private CountDownLatch countDownLatch;
@Override
public void releaseLock() {
if (null != zkClient) {
//删除节点
zkClient.delete(NODE_NAME);
zkClient.close();
System.out.println(Thread.currentThread().getName()+"-释放锁成功");
}
}
//直接创建临时节点,如果创建成功,则表示获取了锁,创建不成功则处理异常
@Override
public boolean tryLock() {
if (null == zkClient) return false;
try {
zkClient.createEphemeral(NODE_NAME);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public void waitLock() {
//监听器
IZkDataListener iZkDataListener = new IZkDataListener() {
//节点被删除回调
@Override
public void handleDataDeleted(String dataPath) throws Exception {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
//节点改变被回调
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
// TODO Auto-generated method stub
}
};
zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
//如果存在则阻塞
if (zkClient.exists(NODE_NAME)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 等待获取锁...");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//删除监听
zkClient.unsubscribeDataChanges(NODE_NAME, iZkDataListener);
}
}
```
SimpleZkLock 表示简单的zk分布式锁,逻辑还是相对比较简单,下面看下测试
LockTest.java
```
package zklock;
public class LockTest {
public static void main(String[] args) {
//模拟多个10个客户端
for (int i=0;i<10;i++) {
Thread thread = new Thread(new LockRunnable());
thread.start();
}
}
static class LockRunnable implements Runnable{
@Override
public void run() {
AbstractLock zkLock = new SimpleZkLock();
//AbstractLock zkLock = new HighPerformanceZkLock();
zkLock.getLock();
//模拟业务操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
zkLock.releaseLock();
}
}
}
```
测试结果:
打印比较多,还没有全部截完...这个时候我们也能看出使用zk实现的简单的分布式锁存在的性能问题
二、高性能分布式锁
上面使用zk实现的简单的分布式锁,实现比较简单,但是存在性能问题,从上面的打印的结果可以看出、每一次客户端释放锁的时候,其他的客户端都会去抢锁,这就造成了不必要的浪费。那么如果提升性能呢?
1、思路:客户端在抢锁的时候进行排队,客户端只要监听它前一个节点的变化就行,如果前一个节点释放了锁,客户端才去进行抢锁操作,这个时候我们就需要创建顺序节点了
2、图解
(1)客户端排队
![](https://img.kancloud.cn/2b/c7/2bc7604006d0ab2f4fc0495861871d3d_118x268.png)
(2)获取锁的逻辑
![](https://img.kancloud.cn/bb/c9/bbc9a52c0d4db4f613ada7c4fa4b17d1_381x558.png)
3、代码实现
HighPerformanceZkLock .java
```
package zklock;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;
/**
* 高性能分布式锁
* @author hongtaolong
*
*/
public class HighPerformanceZkLock extends AbstractLock {
private static final String PATH = "/highPerformance_zklock";
//当前节点路径
private String currentPath;
//前一个节点的路径
private String beforePath;
private CountDownLatch countDownLatch = null;
public HighPerformanceZkLock() {
//如果不存在这个节点,则创建持久节点
if (!zkClient.exists(PATH)) {
zkClient.createPersistent(PATH);
}
}
@Override
public void releaseLock() {
if (null != zkClient) {
zkClient.delete(currentPath);
zkClient.close();
}
}
@Override
public boolean tryLock() {
//如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
if (null == currentPath || "".equals(currentPath)) {
//在path下创建一个临时的顺序节点
currentPath = zkClient.createEphemeralSequential(PATH+"/", "lock");
}
//获取所有的临时节点,并排序
List<String> childrens = zkClient.getChildren(PATH);
Collections.sort(childrens);
if (currentPath.equals(PATH+"/"+childrens.get(0))) {
return true;
}else {//如果当前节点不是排名第一,则获取它前面的节点名称,并赋值给beforePath
int pathLength = PATH.length();
int wz = Collections.binarySearch(childrens, currentPath.substring(pathLength+1));
beforePath = PATH+"/"+childrens.get(wz-1);
}
return false;
}
@Override
public void waitLock() {
IZkDataListener lIZkDataListener = new IZkDataListener() {
@Override
public void handleDataDeleted(String dataPath) throws Exception {
if (null != countDownLatch){
countDownLatch.countDown();
}
}
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
};
//监听前一个节点的变化
zkClient.subscribeDataChanges(beforePath, lIZkDataListener);
if (zkClient.exists(beforePath)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(beforePath, lIZkDataListener);
}
}
```
这里只要帖高性能锁的代码了,AbstractLock没变化,LockTest中只要修改一行代码
//AbstractLock zkLock = new SimpleZkLock();
AbstractLock zkLock = new HighPerformanceZkLock();
测试结果:
![](https://img.kancloud.cn/c3/2b/c32bc7bd441c3bad640cbfdc02b5af4f_277x385.png)
上面是全部的打印结果,可以明显看出要比上面简单实现的分布式锁少很多,这说明性能比上面的好,因为它不会去做无用功
————————————————
版权声明:本文为CSDN博主「菜鸟的奋斗ing」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/hongtaolong/article/details/88898875
- 一.JVM
- 1.1 java代码是怎么运行的
- 1.2 JVM的内存区域
- 1.3 JVM运行时内存
- 1.4 JVM内存分配策略
- 1.5 JVM类加载机制与对象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面试相关文章
- 2.1 可能是把Java内存区域讲得最清楚的一篇文章
- 2.0 GC调优参数
- 2.1GC排查系列
- 2.2 内存泄漏和内存溢出
- 2.2.3 深入理解JVM-hotspot虚拟机对象探秘
- 1.10 并发的可达性分析相关问题
- 二.Java集合架构
- 1.ArrayList深入源码分析
- 2.Vector深入源码分析
- 3.LinkedList深入源码分析
- 4.HashMap深入源码分析
- 5.ConcurrentHashMap深入源码分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的设计模式
- 8.集合架构之面试指南
- 9.TreeSet和TreeMap
- 三.Java基础
- 1.基础概念
- 1.1 Java程序初始化的顺序是怎么样的
- 1.2 Java和C++的区别
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字节与字符的区别以及访问修饰符
- 1.7 深拷贝与浅拷贝
- 1.8 字符串常量池
- 2.面向对象
- 3.关键字
- 4.基本数据类型与运算
- 5.字符串与数组
- 6.异常处理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 数据流(Stream)
- 8.3 Java 8 并发教程:线程和执行器
- 8.4 Java 8 并发教程:同步和锁
- 8.5 Java 8 并发教程:原子变量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、数值、算术和文件
- 8.7 在 Java 8 中避免 Null 检查
- 8.8 使用 Intellij IDEA 解决 Java 8 的数据流问题
- 四.Java 并发编程
- 1.线程的实现/创建
- 2.线程生命周期/状态转换
- 3.线程池
- 4.线程中的协作、中断
- 5.Java锁
- 5.1 乐观锁、悲观锁和自旋锁
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平锁和非公平锁
- 5.3.1 说说ReentrantLock的实现原理,以及ReentrantLock的核心源码是如何实现的?
- 5.5 锁优化和升级
- 6.多线程的上下文切换
- 7.死锁的产生和解决
- 8.J.U.C(java.util.concurrent)
- 0.简化版(快速复习用)
- 9.锁优化
- 10.Java 内存模型(JMM)
- 11.ThreadLocal详解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的实现原理
- 1.DelayQueue的实现原理
- 14.Thread.join()实现原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的实际使用场景
- 五.Java I/O NIO
- 1.I/O模型简述
- 2.Java NIO之缓冲区
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之选择器
- 6.基于 Java NIO 实现简单的 HTTP 服务器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面试题
- 六.Java设计模式
- 1.单例模式
- 2.策略模式
- 3.模板方法
- 4.适配器模式
- 5.简单工厂
- 6.门面模式
- 7.代理模式
- 七.数据结构和算法
- 1.什么是红黑树
- 2.二叉树
- 2.1 二叉树的前序、中序、后序遍历
- 3.排序算法汇总
- 4.java实现链表及链表的重用操作
- 4.1算法题-链表反转
- 5.图的概述
- 6.常见的几道字符串算法题
- 7.几道常见的链表算法题
- 8.leetcode常见算法题1
- 9.LRU缓存策略
- 10.二进制及位运算
- 10.1.二进制和十进制转换
- 10.2.位运算
- 11.常见链表算法题
- 12.算法好文推荐
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事务管理
- 4.SpringMVC 运行流程和手动实现
- 0.Spring 核心技术
- 5.spring如何解决循环依赖问题
- 6.springboot自动装配原理
- 7.Spring中的循环依赖解决机制中,为什么要三级缓存,用二级缓存不够吗
- 8.beanFactory和factoryBean有什么区别
- 九.数据库
- 1.mybatis
- 1.1 MyBatis-# 与 $ 区别以及 sql 预编译
- Mybatis系列1-Configuration
- Mybatis系列2-SQL执行过程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-参数设置揭秘(ParameterHandler)
- Mybatis系列8-缓存机制
- 2.浅谈聚簇索引和非聚簇索引的区别
- 3.mysql 证明为什么用limit时,offset很大会影响性能
- 4.MySQL中的索引
- 5.数据库索引2
- 6.面试题收集
- 7.MySQL行锁、表锁、间隙锁详解
- 8.数据库MVCC详解
- 9.一条SQL查询语句是如何执行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能优化神器 Explain 使用分析
- 12.mysql中,一条update语句执行的过程是怎么样的?期间用到了mysql的哪些log,分别有什么作用
- 十.Redis
- 0.快速复习回顾Redis
- 1.通俗易懂的Redis数据结构基础教程
- 2.分布式锁(一)
- 3.分布式锁(二)
- 4.延时队列
- 5.位图Bitmaps
- 6.Bitmaps(位图)的使用
- 7.Scan
- 8.redis缓存雪崩、缓存击穿、缓存穿透
- 9.Redis为什么是单线程、及高并发快的3大原因详解
- 10.布隆过滤器你值得拥有的开发利器
- 11.Redis哨兵、复制、集群的设计原理与区别
- 12.redis的IO多路复用
- 13.相关redis面试题
- 14.redis集群
- 十一.中间件
- 1.RabbitMQ
- 1.1 RabbitMQ实战,hello world
- 1.2 RabbitMQ 实战,工作队列
- 1.3 RabbitMQ 实战, 发布订阅
- 1.4 RabbitMQ 实战,路由
- 1.5 RabbitMQ 实战,主题
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 实战 – 整合 RabbitMQ 发送邮件
- 1.8 RabbitMQ 的消息持久化与 Spring AMQP 的实现剖析
- 1.9 RabbitMQ必备核心知识
- 2.RocketMQ 的几个简单问题与答案
- 2.Kafka
- 2.1 kafka 基础概念和术语
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志机制
- 2.4 kafka是pull还是push的方式传递消息的?
- 2.5 Kafka的数据处理流程
- 2.6 Kafka的脑裂预防和处理机制
- 2.7 Kafka中partition副本的Leader选举机制
- 2.8 如果Leader挂了的时候,follower没来得及同步,是否会出现数据不一致
- 2.9 kafka的partition副本是否会出现脑裂情况
- 十二.Zookeeper
- 0.什么是Zookeeper(漫画)
- 1.使用docker安装Zookeeper伪集群
- 3.ZooKeeper-Plus
- 4.zk实现分布式锁
- 5.ZooKeeper之Watcher机制
- 6.Zookeeper之选举及数据一致性
- 十三.计算机网络
- 1.进制转换:二进制、八进制、十六进制、十进制之间的转换
- 2.位运算
- 3.计算机网络面试题汇总1
- 十四.Docker
- 100.面试题收集合集
- 1.美团面试常见问题总结
- 2.b站部分面试题
- 3.比心面试题
- 4.腾讯面试题
- 5.哈罗部分面试
- 6.笔记
- 十五.Storm
- 1.Storm和流处理简介
- 2.Storm 核心概念详解
- 3.Storm 单机版本环境搭建
- 4.Storm 集群环境搭建
- 5.Storm 编程模型详解
- 6.Storm 项目三种打包方式对比分析
- 7.Storm 集成 Redis 详解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初识ElasticSearch
- 2.文档基本CRUD、集群健康检查
- 3.shard&replica
- 4.document核心元数据解析及ES的并发控制
- 5.document的批量操作及数据路由原理
- 6.倒排索引
- 十七.分布式相关
- 1.分布式事务解决方案一网打尽
- 2.关于xxx怎么保证高可用的问题
- 3.一致性hash原理与实现
- 4.微服务注册中心 Nacos 比 Eureka的优势
- 5.Raft 协议算法
- 6.为什么微服务架构中需要网关
- 0.CAP与BASE理论
- 十八.Dubbo
- 1.快速掌握Dubbo常规应用
- 2.Dubbo应用进阶
- 3.Dubbo调用模块详解
- 4.Dubbo调用模块源码分析
- 6.Dubbo协议模块