基于redis分布式锁实现的多线程并发程序

前两个版本的代码 都或多或少存在一定的问题,虽然可能微乎其微,但是程序需要严谨再严谨,
第一个版本问题:  局限于单机版,依赖于 Jvm的锁 
第二个版本问题:  极端情况下,解锁逻辑的问题,线程B的锁,可能会被线程A解掉,这种情况实际上是不合理的。
1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。 
2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,
但是这个客户端的锁的过期时间可能被其他客户端覆盖。
3. 锁不具备拥有者标识,即任何客户端都可以解锁。
版本一: http://www.cnblogs.com/xifenglou/p/8807323.html
版本二: http://www.cnblogs.com/xifenglou/p/8883717.html所以基于以上问题,第三个版本出来了,
Talk is cheap, show me the code!import org.springframework.util.StopWatch;
import redis.clients.jedis.Jedis;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/***  使用RedisTool.tryGetDistributedLock*  实现 分布式锁*  终极版本*/
public class TicketRunnable3 implements Runnable {private CountDownLatch count;private CyclicBarrier barrier;private static final Integer Lock_Timeout = 10000;private static final String lockKey = "LockKey";private volatile static  boolean  working  = true;public TicketRunnable3(CountDownLatch count, CyclicBarrier barrier) {this.count = count;this.barrier = barrier;}private int num = 20;  // 总票数 此处可随意 写一个数,保证线程能运行起来,真正的共享变量不应该写死在程序中, 应该从redis中获取,这样模拟多进程多线程的并发访问public void sellTicket(Jedis jedis) {String name = Thread.currentThread().getName();try{boolean getLock = RedisTool.tryGetDistributedLock(jedis,lockKey, name,Lock_Timeout);if( getLock){if(!working)return;// Do your jobnum = Integer.parseInt(jedis.get("ticket"));if (num > 0) {num--;jedis.set("ticket",num+"");if(num!=0)System.out.println("================"+Thread.currentThread().getName()+"=================  售出票号" + (num+1)+",还剩" + num + "张票--" );else {System.out.println("================"+Thread.currentThread().getName()+"=================  售出票号" + (num+1)+",票已经票完!--");working = false;}}}else{//System.out.println();if(!working)return;System.out.println(Thread.currentThread().getName()+" Try to get the Lock,and wait 20 millisecond....");Thread.sleep(10);}}catch(Exception e){System.out.println(e);}finally {try {if(RedisTool.releaseDistributedLock(jedis,lockKey,name)){Thread.sleep(30);}}catch (Exception e ) {e.printStackTrace();}}}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"到达,等待中...");Jedis jedis = new Jedis("localhost", 6379);try{barrier.await();    // 此处阻塞  等所有线程都到位后 一起进行抢票if(Thread.currentThread().getName().equals("pool-1-thread-1")){System.out.println("-----------------全部线程准备就绪,开始抢票------------------");}else {Thread.sleep(5);}while (working) {sellTicket(jedis);}count.countDown();  //当前线程结束后,计数器-1}catch (Exception e){e.printStackTrace();}}/**** @param args*/public static void main(String[] args) {int threadNum = 5;    //模拟多个窗口 进行售票final CyclicBarrier barrier = new CyclicBarrier(threadNum);final CountDownLatch count = new CountDownLatch(threadNum);  // 用于统计 执行时长StopWatch watch = new StopWatch();watch.start();TicketRunnable3 tickets = new TicketRunnable3(count,barrier);ExecutorService executorService = Executors.newFixedThreadPool(threadNum);//ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < threadNum; i++) {   //此处 设置数值  受限于 线程池中的数量executorService.submit(tickets);}try {count.await();executorService.shutdown();watch.stop();System.out.println("耗 时:" + watch.getTotalTimeSeconds() + "秒");} catch (InterruptedException e) {e.printStackTrace();}}
}
import redis.clients.jedis.Jedis;
import java.util.Collections;public class RedisTool {private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";private static final Long RELEASE_SUCCESS = 1L;/** * 尝试获取分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @param expireTime 超期时间* @return 是否获取成功*/public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {System.out.println("=============="+Thread.currentThread().getName()+"===============  获取到锁,开始工作!");return true;}return false;}/*** 释放分布式锁* @param jedis Redis客户端* @param lockKey 锁* @param requestId 请求标识* @return 是否释放成功*/public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {System.out.println("=============="+Thread.currentThread().getName()+"===============  解锁成功!");return true;}return false;}}  

解锁部分,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。

那么这段Lua代码的功能是什么呢?其实很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的。源于Redis的特性,下面是官网对eval命令的部分解释:

简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。

 

 

 

运行结果如下:

 

 欢迎留言,期待更深层次的探讨!

 

针对 上述代码,使用两个类 运行,

TicketRunnable3  TicketRunnable4 模拟多进程  多线程场景 ,

场景1: 运行时长 > 过期时长    

此时: 锁自动失效, 线程均不用解锁,即使解锁也是失败!

代码及运行结果如下:

import org.springframework.util.StopWatch;
import redis.clients.jedis.Jedis;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/***  使用RedisTool.tryGetDistributedLock*  实现 分布式锁*  终极版本*/
public class TicketRunnable4 implements Runnable {private CountDownLatch  count;private CyclicBarrier  barrier;private static final Integer  Lock_Timeout = 3000;    // 过期时间 代表 3秒后过期private static final Integer  ExecuteTime = 5000;private static final Integer  RetryInterval = 20;private static final String  lockKey = "LockKey";private volatile static  boolean  working  = true;public TicketRunnable4(CountDownLatch count, CyclicBarrier barrier) {this.count = count;this.barrier = barrier;}private int num = 20;  // 总票数public void sellTicket(Jedis jedis) {String name = Thread.currentThread().getName();boolean gotLock = false;try{gotLock = RedisTool.tryGetDistributedLock(jedis,lockKey, name,Lock_Timeout);if( gotLock && working){// Do your jobnum = Integer.parseInt(jedis.get("ticket"));if (num > 0) {num--;jedis.set("ticket",num+"");if(num!=0)System.out.println("=============="+name+"===============  售出票号" + (num+1)+",还剩" + num + "张票--" );else {System.out.println("=============="+name+"===============  售出票号" + (num+1)+",票已经票完!--");return;}}if(num == 0){System.out.println("=============="+name+"============票已经被抢空啦");working = false;}Thread.sleep(ExecuteTime);}else{//System.out.println();//System.out.println(name+" Try to get the Lock,and wait "+RetryInterval+" millisecond....");Thread.sleep(RetryInterval);}}catch(Exception e){System.out.println(e);}finally {try {if(!gotLock||!working) //未获取到锁的线程不用解锁return;/*** 解锁成功后 sleep, 尝试让出cpu给其他线程机会* 解锁失败 说明锁已经失效 被其他线程获取到*/if(RedisTool.releaseDistributedLock(jedis,lockKey,name)){Thread.sleep(100);}}catch (Exception e ) {e.printStackTrace();}}}@Overridepublic void run() {String prefix = "#";String threadName = Thread.currentThread().getName();Thread.currentThread().setName(prefix+threadName);System.out.println(Thread.currentThread().getName()+"到达,等待中...");Jedis jedis = new Jedis("localhost", 6379);try{barrier.await();    // 此处阻塞  等所有线程都到位后 一起进行抢票if(Thread.currentThread().getName().equals(prefix+"pool-1-thread-2")){System.out.println("-----------------全部线程准备就绪,开始抢票------------------");}else {Thread.sleep(5);}while (working) {sellTicket(jedis);}count.countDown();  //当前线程结束后,计数器-1}catch (Exception e){e.printStackTrace();}}/**** @param args*/public static void main(String[] args) {int threadNum = 3;    //模拟多个窗口 进行售票final CyclicBarrier barrier = new CyclicBarrier(threadNum);final CountDownLatch count = new CountDownLatch(threadNum);  // 用于统计 执行时长StopWatch watch = new StopWatch();watch.start();TicketRunnable4 tickets = new TicketRunnable4(count,barrier);ExecutorService executorService = Executors.newFixedThreadPool(threadNum);//ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < threadNum; i++) {   //此处 设置数值  受限于 线程池中的数量executorService.submit(tickets);}try {count.await();executorService.shutdown();watch.stop();System.out.println("耗 时:" + watch.getTotalTimeSeconds() + "秒");} catch (InterruptedException e) {e.printStackTrace();}}
}

TicketRunnable3 售出 10 9 8 6 5 3 2 1 票号。

TicketRunnable4售出  7 4  两个票号

合计10张票,模拟结束!

场景2: 运行时长 < 过期时长    

此时: 此时需要有锁线程去释放锁,这样多线程再去竞争获取锁。

 

修改代码:

 

private static final Integer  Lock_Timeout = 5000;    // 将时间从3秒改为5秒
private static final Integer  ExecuteTime = 3000;    //  将执行时间5秒改为3秒运行结果如下:

 

一个进程售出 10  9 8 6 4 3 1 票号
另一进程售出 7 5 2  票号
此时 每个线程完成任务后,均需要释放锁,这样本地线程或是异地线程 才能获取到锁,这样才能有机会进行任务的执行!

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

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

相关文章

day15 Ui自动化元素的定位

day15 元素的定位Ui自动化元素的定位1、火狐浏览器安装try xpath2、元素定位思路&#xff1a;&#xff08;1&#xff09;查看页面元素&#xff0c;确认能够唯一定位到元素的属性&#xff0c;比如id&#xff0c;文案3、学习xpath cssSelector 手写定位方式xpath&#xff08;xpat…

几张图可以理解GC JVM调优的内容

public class ApiPurchaseOrderServiceApp {public static void main(String[] args) throws Exception {ApiPurchaseOrderServiceApp mnew ApiPurchaseOrderServiceApp();m.compute();//栈System.out.println("ok");//方法出口}public int compute(){int a1;//局部…

灵魂拷问!一起刷完了这份1307页的安卓面试宝典吧,不吃透都对不起自己

前言 每个程序员都有一个梦想&#xff0c;那就是进一线互联网公司深造&#xff0c;不要跟我说你不想进去&#xff0c;如果给你一个这样的平台&#xff0c;不管是薪资待遇还是接触的高度来说&#xff0c;对我们程序员来说都是一个机会&#xff0c;我以前有一个同事&#xff0c;…

ShardingSphere分库分表实战

ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈&#xff0c;它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar&#xff08;计划中&#xff09;这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能&#xff0c;可适用于…

灵魂拷问!细数Android开发者的艰辛历程,成功入职阿里

什么是中年危机 根据权威数据显示&#xff0c;国内IT程序员鼎盛时期是在25-27岁左右&#xff0c;30岁对于程序员而言完全是一个38线&#xff0c;接着就是转业转岗的事情&#xff0c;这一点在业界也算是一个共识了。 大学毕业步入IT行业普遍年龄也是在22岁左右&#xff0c;然而…

JMM模型到并发编程

电脑&#xff1a;内存 L1 L2 L3 缓存 CPU ctrlatldel就可以看到 Java 有线程内存&#xff0c;在执行线程的时候&#xff0c;会从主内存把变量加载到工作内存&#xff08;缓存&#xff09;&#xff0c;所以&#xff0c;在多线程同时改变一个静态变量时候&#xff0c;实际是分开…

焦虑的移动互联网开发者如何破局?专题解析

尴尬的35岁 不知道是哪个人提出的职场35岁就要面临被淘汰的定律&#xff0c;因为35岁定律本来就是个伪命题&#xff0c;尤其是在IT行业! 现在年八九百万的大学生毕业&#xff0c;他们虽然年轻活力&#xff0c;但是很多企业也将之“拒之门外”。 35岁的不要&#xff0c;二十几…

17.前端路由router-07keep-alive

keep-alive是Vue提供的一个抽象组件&#xff0c;用来对组件进行缓存&#xff0c;从而节省性能&#xff0c; 由于是一个抽象组件&#xff0c;所以在v页面渲染完毕后不会被渲染成一个DOM元素 当组件在keep-alive内被切换时组件的activated、deactivated这两个生命 周期钩子函数会…

使用IDEA创建Maven项目和Maven使用入门(配图详解)

本文详解的讲解了使用IDEA创建Maven项目&#xff0c;及Maven的基础入门。 1、打开IDEA&#xff0c;右上角选择File->New->Project 2、如图中所示选择Maven&#xff08;可按自己所需添加&#xff0c;否则加载时速度很慢&#xff09; 3、添加项目所需Groupld,ArtifactId,Ve…

jvisualvm安装Visual GC插件

给jdk自带的jvisualvm安装Visual GC插件&#xff0c;遇到Were sorry the java.net site has closed&#xff08;我们很抱歉java.net网站已经关闭&#xff09; 1、找到新的更新地址 visualvm新访问地址&#xff1a;https://visualvm.github.io/index.html 进入“Plugins”&…

来自阿里巴巴佛系安卓程序员的指南,专题解析

开头 中国互联网发展的这些年&#xff0c;如今90后程序员是中国程序员的主力军&#xff0c;互联网的热潮也让一批批00后蠢蠢欲动&#xff0c;尝试涌入互联网圈。 当程序员容易&#xff0c;当一个优秀的程序员需要不断学习&#xff0c;从初级程序员到高级程序员&#xff0c;从…

C#在WinForm中打开控制台显示

引用&#xff1a; namespace 测试使用 {public partial class Form1 : Form{[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError true)][return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bo…

Redis+AQS

前言 对于java的单进程应用来说&#xff0c;存在资源竞争的场景可以使用synchronized关键字和Lock来对资源进行加锁&#xff0c;使整个操作具有原子性。但是对于多进程或者分布式的应用来说&#xff0c;上面提到的锁不共享&#xff0c;做不到互相通讯&#xff0c;所以就需要分…

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;有家庭&…