一 问题背景

我们做的是医疗信息化系统,在系统中一条患者信息对医院中当前科室中的所有诊断医生是可见的,当有一个诊断医生点击按钮处理该数据时,数据的状态发生了变化,其他的医生就不可以再处理此患者的数据了。我们开始的做法是,在医生点击按钮时先去后台数据库获取当前数据状态,根据状态判断数据是否可以操作,如果可以操作,则修改数据状态,进行业务逻辑处理,否则提示数据已被其他人处理,不能处理。

二 问题分析

按照上边的业务逻辑,我们画个图分析,如下图

在上图中,如果用户A和B同时向数据库发起请求获取数据状态,数据库返回wait,A和B都拿到了相同的状态,判断是可以操作数据的,这时他们处理数据。A用户处理完成后提交了数据,数据库状态变为done,记录此数据的处理人为A。由于B用户也可以处理数据,所以他也提交数据,这时数据的操作人记录为了B。有人会说,在A和B提交数据修改状态时再做一个状态的判断,这种也难以避免最开始的获取状态的问题,即使这一步状态获取到了,提示后边的人不能修改,这又会产生系统不友好的问题(我操作了半天,到最后你告诉我不能处理,我白忙活了)。以上问题产生的主要原因就是在多线程情况下对共享数据的资源竞争处理不当,我们需要保证数据的唯一性,即在某一时刻,只能有一个线程独享数据资源。

三 问题解决

如何解决呢?分布式锁,分布式锁有多种实现方式,本文我们用redis实现。由于redis是单线程的,所以一次只能处理一个请求,并将资源分配给这个请求,我们称加 锁。如下图

多线程情况下,redis只会处理其中的一个,其他的暂时等待。如上图当A和B同时发出请求时,redis接受并处理A请求,此时B请求排队等待,等到A请求处理完后再处理B请求。此时redis已经将资源(lock)分配给了A,A请求数据库,B请求没有获取到资源直接返回不在请求数据库。这样就保证了数据库共享数据被唯一资源使用。代码简单实现

 public class RedisLock {

     private static final String GET_RESULT = "OK";
private static final String RELEASE_RESULT = "1";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; /**
* 获取redis锁
* @param jedis redis客户端
* @param lockKey 锁标识 key
* @param requestId 锁的持有者,加锁的请求
* @param expireTime 锁过期时间
* @return
*/
public static boolean getLock(Jedis jedis, String lockKey, String requestId, int expireTime){
//SET_IF_NOT_EXIST 当key不存在时 才处理
//SET_WITH_EXPIRE_TIME 设置过期时间 时间由expireTime决定
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (GET_RESULT.equals(result)) {
return true;
}
return false;
} /**
* 释放锁
* @param jedis
* @param lockKey
* @param requestId
* @return
*/
public static boolean releaseLock(Jedis jedis, String lockKey, String requestId){
// 方式1
// if (jedis.get(lockKey).equals(requestId)) {//校验当前锁的持有人与但概念请求是否相同
// 执行在这里时,如果锁被其它请求重新获取到了,此时就不该删除了
// jedis.del(lockKey);
// } //方式2
// eval() 方法会交给redis服务端执行,减少了从服务端再到客户端处理的过程
//赋值 KEYS[1] = lockKey ARGV[1] = requestId
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object releaseResult = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_RESULT.equals(releaseResult.toString())) {
return true;
}
return false;
}
}

四 测试锁机制

  测试并发我们可以使用一些软件,比如Jmeter,本文我们写个方法测试

 public static void main(String[] args) {
//要创建的线程的数量
CountDownLatch looker = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(10);
final String key = "lockKey";
for(int i=0; i < latch.getCount(); i++){
Jedis jedis = new Jedis();
UUID uuid = UUID.randomUUID();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
looker.await();
System.out.println(Thread.currentThread().getName()+"竞争资源,获取锁");
boolean getResult = getLock(jedis, key, uuid.toString(), 5000);
if(getResult){
System.out.println(Thread.currentThread().getName()+"获取到了锁,处理业务,用时3秒");
Thread.sleep(3000);
boolean releaseResult = releaseLock(jedis, key, uuid.toString());
if(releaseResult){
System.out.println(Thread.currentThread().getName()+"业务处理完毕,释放锁");
}
}else{
System.out.println(Thread.currentThread().getName()+"竞争资源失败,未获取到锁");
}
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
} try {
System.out.println("准备,5秒后开始");
Thread.sleep(5000);
looker.countDown(); //发令 let all threads proceed latch.await(); // // wait for all to finish
System.out.println("结束");
} catch (InterruptedException e) {
e.printStackTrace();
} }

可以看到控制台上输出的结果

多线程并发问题解决之redis锁的更多相关文章

  1. 多线程并发编程之显示锁ReentrantLock和读写锁

    在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是 ...

  2. java多线程并发编程中的锁

    synchronized: https://www.cnblogs.com/dolphin0520/p/3923737.html Lock:https://www.cnblogs.com/dolphi ...

  3. spring quartz使用多线程并发“陷阱”

    定义一个job:ranJob,设置每秒执行一次,设置不允许覆盖并发执行 <bean id="rankJob" class="com.chinacache.www.l ...

  4. (实例篇)php 使用redis锁限制并发访问类示例

    1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制 ...

  5. php 使用redis锁限制并发访问类

    1.并发访问限制问题 对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功. 例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制 ...

  6. 多线程并发 synchronized对象锁的控制与优化

    本文针对用户取款时多线程并发情境,进行相关多线程控制与优化的描述. 首先建立用户类UserTest.业务操作类SynchronizedTest.数据存取类DataStore,多线程测试类MultiTh ...

  7. redis锁处理并发问题

    redis锁处理并发问题 redis锁处理高并发问题十分常见,使用的时候常见有几种错误,和对应的解决办法. set方式 setnx方式 setnx+getset方式 set方式 加锁:redis中se ...

  8. redis 初步认识四(redis锁,防并发)

    using System; namespace ConsoleAppRedis { class Program { static void Main(string[] args) { //第一种,无登 ...

  9. 利用Redis锁解决高并发问题

    这里我们主要利用Redis的setnx的命令来处理高并发. setnx 有两个参数.第一个参数表示键.第二个参数表示值.如果当前键不存在,那么会插入当前键,将第二个参数做为值.返回 1.如果当前键存在 ...

随机推荐

  1. Java定时任务的实现

    本例依据Java自身提供的接口实现,通过监听器(Listener)和定时器(Timer)定时执行某个任务(Task).专业的开源工具可参考Quartz:http://www.opensymphony. ...

  2. Linux FIO

    FIO是测试IOPS的非常好的工具,用来对硬件进行压力测试和验证,支持13种不同的I/O引擎,包括:sync,mmap, libaio, posixaio, SG v3, splice, null, ...

  3. Android 属性动画实现一个简单的PopupWindow

    1.今天看到一个PopupWindow的效果如图: 2.其实就是属性动画的一个简单实用就ObjectAnimator就可以的,想实现更多,更灵活的可以用ValueAnimator 3.直接上代码: p ...

  4. linux 常用命令,开发记住这些基本能够玩转linux

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...

  5. 构建命令maven install 打包不是最新的代码

    问题: 之前一直用的是mvn install 命令来构建项目,但是最近发现最新的代码没有在war包中.之前看的说 mvn install 命令会执行之前的所有阶段,会被编译,测试,打包. 经查最后采用 ...

  6. 再编写代码中报错:CS8107 C# 7.0 中不支持功能“xxxxxx”。请使用 7.1 或更高的语言版本。

    解决方法:项目右键属性 ---> 生成 ---> 找到最下面的高级按钮,点击高级按钮 ---> 常规 ---> 语言版本 ---> 选择 C#最新次要版本,或者比当前版本 ...

  7. cannot connect cube with sharepoint dashboard designer

    需要下载WindowsIdentityFoundation-SDK-4.0进行安装

  8. python网络编程--线程(锁,GIL锁,守护线程)

    1.线程 1.进程与线程 进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率.很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观 ...

  9. WPF Adorner 在TabControl切换TabItem时消失

    错误的截图: 一开始以为是MVVM绑定的代码中出现了问题,但是通过断点追踪并没有发现问题. 通过通过VS的实时可视化树发现问题:切换Item时Adorner会在AdornerLayer直接消失.届时怀 ...

  10. linux网络流量实时监控工具之iptraf 【个人比较喜欢用的流量监控软件】

    linux网络流量实时监控工具之iptraf IPTraf是一个网络监控工具,功能比nload更强大,可以监控所有的流量,IP流量,按协议分的流量,还可以设置过滤器等,如下图 对监控网络来说,这个更适 ...