Redis+AQS

前言

对于java的单进程应用来说,存在资源竞争的场景可以使用synchronized关键字和Lock来对资源进行加锁,使整个操作具有原子性。但是对于多进程或者分布式的应用来说,上面提到的锁不共享,做不到互相通讯,所以就需要分布式锁来解决问题了。
废话不多说,直接进入正题,下面结合AQS和Redis来实现分布式锁。

代码中大部分都是参考ReentrantLock来实现的,所以读者可以先去了解一下ReentranLock和AQS
参阅:
http://www.importnew.com/27477.html
http://cmsblogs.com/?p=2210


加锁

 @Overrideprotected boolean tryAcquire(int acquires) throws AcquireLockTimeoutException {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() &&compareAndSetState(0, 1)) {      // 标注1setExclusiveOwnerThread(current);// 如果是线程被中断失败的话,返回false,如果超时失败的话,捕获异常return tryAcquireRedisLock(TimeUnit.MILLISECONDS.toNanos(redisLockTimeout));}//可重入} else if (current == getExclusiveOwnerThread()) { //标注2int nextc = c + acquires;if (nextc < 0) {throw new Error("Maximum lock count exceeded");}setState(nextc);return true;}return false;}
  •  

下面会把进程内的锁称为进程锁,如果有更专业的描述方法的话,欢迎指出。

对上面的步骤分析:
1. 首先看标注1,通过compareAndSetState获取到进程锁,只有获取到进程锁,才有资格去竞争redis锁, 这样的好处就是对于同一个进程里面的所有加锁请求,在某一个时刻只有一个请求能去请求获取redis锁,有效降低redis的压力,总的来说就是把部分竞争交给进程自己去解决了,也就是先竞争进程锁。
2. 再看标注2,能进行到这一步,首先能确保已经获取了进程锁,但是是否一定获取了redis锁了呢,不一定,所以在tryAcquireRedisLock的过程中如果抛出异常,一定要保证使用finally代码块把进程锁释放掉,避免误以为已经同时获取了进程锁和redis锁。

获取redis锁

private final boolean tryAcquireRedisLock(long nanosTimeout) {if (nanosTimeout <= 0L) {return false;}final long deadline = System.nanoTime() + nanosTimeout;int count = 0;boolean interrupted = false;Jedis jedis = null;try {jedis = redisHelper.getJedisInstance();while (true) {nanosTimeout = deadline - System.nanoTime();if (nanosTimeout <= 0L) {throw new AcquireLockTimeoutException();}String value = String.format(valueFormat, Thread.currentThread().getId());//避免系统宕机锁不释放,设置过期时间String response = jedis.set(lockKey, value, NX, PX, redisLockTimeout);if (OK.equals(response)) {//如果线程被中断同时也是失败的return !interrupted;}// 超过尝试次数if (count > RETRY_TIMES && nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD && parkAndCheckInterrupt()) {interrupted = true;}count++;}} finally {redisHelper.returnResouce(jedis);}}final boolean parkAndCheckInterrupt() {LockSupport.parkNanos(TimeUnit.NANOSECONDS.toNanos(PARK_TIME));return Thread.interrupted();
}
  •  

分析:
1. 为了避免获取redis锁的过程无休止的运行下去,使用超时策略,如果超时了,直接返回失败
2. 如果还在有效时间内,则通过自旋不断尝试获取锁,如果超过了尝试次数,暂时挂起,让出时间片,但是不可以挂起太长的时间,几个时间片内为好。

解锁

//RedisDistributedLock.java
@Override
public void unlock() {sync.unlock();
}//Sync.java 
public void unlock() {release(1);
}@Override
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {Jedis jedis = null;try {jedis = redisHelper.getJedisInstance();String value = String.format(valueFormat, Thread.currentThread().getId());jedis.eval(UNLOCK_SCRIPT, Arrays.asList(lockKey), Arrays.asList(value));} finally {redisHelper.returnResouce(jedis);}free = true;setExclusiveOwnerThread(null);}setState(c);return free;
}
  •  

分析:
1. 可以注意到value在加锁和解锁的过程都有,这个value是用来标识锁的唯一性的,避免别的进程误删了该锁。

private final UUID uuid = UUID.randomUUID();
private final String valueFormat = "%d:" + uuid.toString();
  •  

验证

@Overridepublic void run() {SqlSession session = MybatisHelper.instance.openSession(true);try {KeyGeneratorMapper generatorMapper = session.getMapper(KeyGeneratorMapper.class);KeyFetchRecordMapper recordMapper = session.getMapper(KeyFetchRecordMapper.class);while (true) {try {lock.lock();KeyGenerator keyGenerator = generatorMapper.select(1);if (keyGenerator.getKey() >= MAX_KEY) {System.exit(0);}recordMapper.insert(new KeyFetchRecord(keyGenerator.getKey(), server));generatorMapper.increase(1, 1);session.commit();} catch (RuntimeException e) {e.printStackTrace();continue;} finally {lock.unlock();}}} finally {session.close();}}
  •  

开启5个进程,每个进程5个线程,进行获取一个key值,获取到后加1,然后记录到数据库,这个过程不要是原子的,因为把没有原子性的过程变成有原子性的过程,才证明了这个锁的有效性。

结果如下

这里写图片描述
这里写图片描述

没有重复的key,成功!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386536.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

disruptor 介绍

一、背景 1.来源 Disruptor是英国外汇交易公司LMAX开发的一个高性能队列&#xff0c;研发的初衷是解决内部的内存队列的延迟问题&#xff0c;而不是分布式队列。基于Disruptor开发的系统单线程能支撑每秒600万订单&#xff0c;2010年在QCon演讲后&#xff0c;获得了业界关注。…

算法题+JVM+自定义View,详细的Android学习指南

前言 想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样~。 学算法真的很痛苦&#xff0c;虽然大数据现在很火&#xff0c;但找到适合自己定位的职业也未尝不是一种合理选择。 投百度的经历非…

用过的前端插件合集

用过的前端插件合集 FontAwesome字体 Font Awesome详细用法参见上述站点的Examples。 SweetAlert系列 SweetAlertSweetAlert2SweetAlert 到 SweetAlert2 升级指南示例&#xff1a; 基本使用&#xff1a; swal("标题","内容","success);使用SweetAlert…

CAS和AQS

CAS 全称&#xff08;Compare And Swap&#xff09;,比较交换 Unsafe类是CAS的核心类&#xff0c;提供硬件级别的原子操作。 // 对象、对象的地址、预期值、修改值 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);缺点&#xf…

系统盘点Android开发者必须掌握的知识点,全网疯传

最近在知乎上&#xff0c;有许多人在邀请我去回答“Android前景怎么样、是不是要凉了、是不是应该考虑要转行&#xff1f;”等一系列的问题。 想着可能有很多人都有这样的担心&#xff0c;于是就赶紧写篇文章&#xff0c;来跟你们谈下Android开发的前景到底怎么样&#xff1f;…

数据库操作DDL

show database; 查看所有数据库 drop database db_name; 删除数据库 create database db_name;创建数据库 一个数据库对应一个文件夹 create database if not exists db_name; show warnings; 查看所有警告 show create databae db_name;查看创建的数据库 create database if n…

细数Android开发者的艰辛历程,已拿offer附真题解析

笼统来说&#xff0c;中年程序员容易被淘汰的原因其实不外乎三点。 1、输出能力已到顶点。这个人奋斗十来年了&#xff0c;依旧碌碌无为&#xff0c;很明显这人的天花板就这样了&#xff0c;说白了&#xff0c;天赋就这样。 2、适应能力越来越差。年纪大&#xff0c;有家庭&…

原子操作类AtomicInteger详解

为什么需要AtomicInteger原子操作类&#xff1f; 对于Java中的运算操作&#xff0c;例如自增或自减&#xff0c;若没有进行额外的同步操作&#xff0c;在多线程环境下就是线程不安全的。num解析为numnum1&#xff0c;明显&#xff0c;这个操作不具备原子性&#xff0c;多线程并…

移动端Rem之讲解总结

日妈常说的H5页面&#xff0c;为啥叫H5页面嘛&#xff0c;不就是手机上展示的页面吗&#xff1f;那是因为啊手机兼容所有html5新特性&#xff0c;所以跑在手机上的页面也叫h5页面&#xff0c;跨平台&#xff08;安装ios),基于webview&#xff0c;它就是终端开发的一个组件&…

终于有人把安卓程序员必学知识点全整理出来了,送大厂面经一份!

除了Bug&#xff0c;最让你头疼的问题是什么&#xff1f;单身&#xff1f;秃头&#xff1f;996?面试造火箭&#xff0c;工作拧螺丝&#xff1f; 作为安卓开发者&#xff0c;除了Bug&#xff0c;经常会碰到下面这些问题&#xff1a; 应用卡顿&#xff0c;丢帧&#xff0c;屏幕画…

mq引入以后的缺点

系统可用性降低? 一旦mq不能使用以后,系统A不能发送消息到mq,系统BCD无法从mq中获取到消息.整个系统就崩溃了. 如何解决: 系统复杂程度增加? 加入mq以后,mq引入来的问题很多,然后导致系统的复杂程度增加. 如何解决 系统的一致性降低? 有人给系统A发送了一个请求,本来这个请求…

网易云的朋友给我这份339页的Android面经,成功入职阿里

IT行业的前景 近几年来&#xff0c;大数据、人工智能AI、物联网等一些技术不断发展&#xff0c;也让人们看到了IT行业的繁荣与良好的前景。越来越多的高校学府加大了对计算机的投入&#xff0c;设立相应的热门专业来吸引招生。当然也有越来越多的人选择从事这个行业&#xff0…

git介绍和常用操作

转载于:https://www.cnblogs.com/kesz/p/11124423.html

网易云的朋友给我这份339页的Android面经,满满干货指导

想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样~。 25%的面试官会在头5分钟内决定面试的结果60%的面试官会在头15分钟内决定面试的结果 一般来说&#xff0c;一场单面的时间在30分钟左右&…

synchronized 和Lock区别

synchronized实现原理 Java中每一个对象都可以作为锁&#xff0c;这是synchronized实现同步的基础&#xff1a; 普通同步方法&#xff0c;锁是当前实例对象静态同步方法&#xff0c;锁是当前类的class对象同步方法块&#xff0c;锁是括号里面的对象 当一个线程访问同步代码块…

美团安卓面试,难道Android真的凉了?快来收藏!

我所接触的Android开发者&#xff0c;百分之九十五以上 都遇到了以下几点致命弱点&#xff01; 如果这些问题也是阻止你升职加薪&#xff0c;跳槽大厂的阻碍。 那么我确信可以帮你突破瓶颈&#xff01; 1.开发者的门越来越高&#xff1a; 小厂的机会少了&#xff0c;大厂…

django -- 实现ORM登录

前戏 上篇文章写了一个简单的登录页面&#xff0c;那我们可不可以实现一个简单的登录功能呢&#xff1f;如果登录成功&#xff0c;给返回一个页面&#xff0c;失败给出错误的提示呢&#xff1f; 在之前学HTML的时候&#xff0c;我们知道&#xff0c;网页在往服务器提交数据的时…

美团点评APP在移动网络性能优化的实践,通用流行框架大全

" 对于程序员来说&#xff0c;如果哪一天开始他停止了学习&#xff0c;那么他的职业生涯便开始宣告消亡。” 高薪的IT行业是众多年轻人的职业梦想&#xff0c;然而&#xff0c;一旦身入其中却发觉没有想像中那么美好。被称为IT蓝领的编程员&#xff0c;工作强度大&#xf…

centos7.0利用yum快速安装mysql8.0

我这里直接使用MySQL Yum存储库的方式快速安装&#xff1a; 抽象 MySQL Yum存储库提供用于在Linux平台上安装MySQL服务器&#xff0c;客户端和其他组件的RPM包。这些软件包还可以升级和替换从Linux发行版本机软件存储库安装的任何第三方MySQL软件包&#xff0c;如果可以从MySQL…

腾讯3轮面试都问了Android事件分发,论程序员成长的正确姿势

前言 这些题目是网友去美团等一线互联网公司面试被问到的题目。笔者从自身面试经历、各大网络社交技术平台搜集整理而成&#xff0c;熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。 主要分为以下几部分&#xff1a; &#xff08;1&#xff09;Android面试题 …