解决线程并发redisson使用遇到的坑

 更新时间:2021年6月18日 15:01  
这篇文章主要介绍了解决线程并发redisson使用遇到的坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

线程并发redisson的坑

背景

因为业务上的一个购买需求,需要对库存进行行程保护,防止超卖的出现(我们不是电商公司),经过调研,最终选择使用Redission来进行控制。

主要因为Redission丰富的API,开源框架,已经被广泛应用于实际生产环境。

问题描述

当我们使用Ression中Lock.lock()方法之后,如果存在线程并发常见情况下,会出现如下异常:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 22

问题分析

在thread-1还没有结束的时候,也就是在thread-1在获得锁但是还没有释放锁的时候, `thread-2由于被别的线程中断停止了等待从lock.tryLock的阻塞状态中返回继续执行接下来的逻辑,并且由于尝试去释放一个属于线程thread-1的锁而抛出了一个运行时异常导致该线程thread-2结束了, 然而thread-2完成了一系列操作后,线程thread-1才释放了自己的锁.

所以thread-2并没有获得锁,却执行了需要同步的内容,还尝试去释放锁。

那解决方式我们就知道了,当前线程加的锁由当前线程去解锁,也就是说当我们使用lock.unlock的时候加上线程的判断即可。

问题解决

 RLock lock = redissonClient.getLock(key);
    if(lock.isLocked()){ // 是否还是锁定状态
      if(lock.isHeldByCurrentThread()){ // 时候是当前执行线程的锁
        lock.unlock(); // 释放锁
      }
    }

redisson使用注意事项

Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格,相较于暴露底层操作的Jedis,Redisson提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。

特性 & 功能:

  • 支持 Redis 单节点(single)模式、哨兵(sentinel)模式、主从(Master/Slave)模式以及集群(Redis Cluster)模式
  • 程序接口调用方式采用异步执行和异步流执行两种方式
  • 数据序列化,Redisson 的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在 Redis 里的读取和存储
  • 单个集合数据分片,在集群模式下,Redisson 为单个 Redis 集合类型提供了自动分片的功能
  • 提供多种分布式对象,如:Object Bucket,Bitset,AtomicLong,Bloom Filter 和 HyperLogLog 等
  • 提供丰富的分布式集合,如:Map,Multimap,Set,SortedSet,List,Deque,Queue 等
  • 分布式锁和同步器的实现,可重入锁(Reentrant Lock),公平锁(Fair Lock),联锁(MultiLock),红锁(Red Lock),信号量(Semaphonre),可过期性信号锁(PermitExpirableSemaphore)等
  • 提供先进的分布式服务,如分布式远程服务(Remote Service),分布式实时对象(Live Object)服务,分布式执行服务(Executor Service),分布式调度任务服务(Schedule Service)和分布式映射归纳服务(MapReduce)
  • 更多特性和功能,请关注官网:http://redisson.org

实现原理

redis本身是不支持上述的分布式对象和集合,Redisson是通过利用redis的特性在客户端实现了高级数据结构和特性,例如优先队列的实现,是通过客户端排序整理后再存入redis。

客户端实现,意味着当没有任何客户端在线时,这些所有的数据结构和特性都不会保留,也不会自动生效,例如过期事件的触发或原来优先队列的元素增加。

注意事项

实时性

RMap中有一个功能是可以设置键值对的过期时间的,并可以注册键值对的事件监听器

元素淘汰功能(Eviction)

Redisson的分布式的RMapCache Java对象在基于RMap的前提下实现了针对单个元素的淘汰机制。同时仍然保留了元素的插入顺序。由于RMapCache是基于RMap实现的,使它同时继承了java.util.concurrent.ConcurrentMap接口和java.util.Map接口。Redisson提供的Spring Cache整合以及JCache正是基于这样的功能来实现的。

目前的Redis自身并不支持散列(Hash)当中的元素淘汰,因此所有过期元素都是通过org.redisson.EvictionScheduler实例来实现定期清理的。为了保证资源的有效利用,每次运行最多清理300个过期元素。任务的启动时间将根据上次实际清理数量自动调整,间隔时间趋于1秒到1小时之间。比如该次清理时删除了300条元素,那么下次执行清理的时间将在1秒以后(最小间隔时间)。一旦该次清理数量少于上次清理数量,时间间隔将增加1.5倍。

正如官方wiki所述,这个功能是通过后台线程定时去清理的, 所以这个是非实时的(issue-1234:on expired event is not executed in real-time.),延迟在5秒到2小时之间,因此对实时性要求比较高的场景就得自己衡量了。

由于过期时间的非实时性,所以导致过期事件的发生也是非实时的,相应的监听器可能会延迟了一会儿才收到通知,在我的测试中,ttl设置在秒级误差是比较大的,分钟级别的ttl倒还好(左侧设置值,右侧实际耗时):

1s _ 5s
3s _ 5s
4s _ 5s
5s _ 9s
6s _ 10s
10s _ 15s
1m _ 1m11s

序列化

由Redisson默认的编码器为JsonJacksonCodec,JsonJackson在序列化有双向引用的对象时,会出现无限循环异常。而fastjson在检查出双向引用后会自动用引用符$ref替换,终止循环。

在我的情况中,我序列化了一个service,这个service已被spring托管,而且和另一个service之间也相互注入了,用fastjson能 正常序列化到redis,而JsonJackson则抛出无限循环异常。

为了序列化后的内容可见,所以不用redission其他自带的二进制编码器,自行实现编码器:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import org.redisson.client.codec.BaseCodec;
import org.redisson.client.protocol.Decoder;
import org.redisson.client.protocol.Encoder;​
import java.io.IOException;​
public class FastjsonCodec extends BaseCodec {​
 private final Encoder encoder = in -> {
 ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
 try {
 ByteBufOutputStream os = new ByteBufOutputStream(out);
 JSON.writeJSONString(os, in,SerializerFeature.WriteClassName);
 return os.buffer();
 } catch (IOException e) {
 out.release();
 throw e;
 } catch (Exception e) {
 out.release();
 throw new IOException(e);
 }
 };
​
 private final Decoder<Object> decoder = (buf, state) ->
 JSON.parseObject(new ByteBufInputStream(buf), Object.class);
​
 @Override
 public Decoder<Object> getValueDecoder() {
 return decoder;
 }
​
 @Override
 public Encoder getValueEncoder() {
 return encoder;
 }
}

订阅发布

Redisson对订阅发布的封装是RTopic,这也是Redisson中很多事件监听的实现原理(例如键值对的事件监听)。

使用单元测试时发现,在事件发布后,订阅方需要延时一下才能收到事件。具体原因待查。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。

相关文章

  • Java 高并发的三种实现案例详解

    这篇文章主要介绍了Java 高并发的三种实现案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-31
  • Java并发教程之Callable和Future接口详解

    Java从发布的第一个版本开始就可以很方便地编写多线程的应用程序,并在设计中引入异步处理,这篇文章主要给大家介绍了关于Java并发教程之Callable和Future接口的相关资料,需要的朋友可以参考下...2021-07-28
  • Java中实现线程间通信的实例教程

    线程通信的目标是使线程间能够互相发送信号,另一方面线程通信使线程能够等待其他线程的信号,这篇文章主要给大家介绍了关于Java中实现线程间通信的相关资料,本文通过示例代码介绍的非常详细,需要的朋友可以参考下...2021-09-07
  • 带你快速搞定java多线程

    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下...2021-07-14
  • java实现/创建线程的几种方式小结

    在JAVA中,用Thread类代表线程,所有线程对象都必须是Thread类或者Thread类子类的实例,下面这篇文章主要介绍了java实现/创建线程的几种方式,需要的朋友可以参考下...2021-08-09
  • 手把手带你了解python多进程,多线程

    这篇文章主要介绍了python多线程与多进程及其区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-08-20
  • C#多线程开发实战记录之线程基础

    线程是一个独立的运行单元,每个进程内部有多个线程,每个线程可以各自同时执行指令,每个线程有自己独立的栈,但是与进程内的其他线程共享内存,这篇文章主要给大家介绍了关于C#多线程开发实战记录之线程基础的相关资料,需要的朋友可以参考下...2021-09-03
  • java多线程模拟实现售票功能

    这篇文章主要为大家详细介绍了java多线程模拟实现售票功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-08-27
  • Java多线程之 FutureTask:带有返回值的函数定义和调用方式

    这篇文章主要介绍了Java多线程之 FutureTask:带有返回值的函数定义和调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-07-14
  • JAVA多线程线程安全性基础

    这篇文章主要介绍了如何测试Java类的线程安全性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2021-08-12
  • 一篇文中细看Java多线程的创建方式

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发,这篇文章主要给大家介绍了如何通过一篇文中细看Java多线程的创建方式,需要的朋友可以参考下...2021-07-18
  • 浅谈为什么单线程的redis那么快

    本文主要介绍了为什么单线程的redis那么快,主要介绍了几点原因,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-16
  • Java并发之synchronized实现原理深入理解

    这篇文章主要介绍了Java中synchronized实现原理详解,涉及synchronized实现同步的基础,Java对象头,Monitor,Mark Word,锁优化,自旋锁等相关内容,具有一定借鉴价值,需要的朋友可以参考下...2021-08-27
  • 一篇文章带你入门java多线程

    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下...2021-08-05
  • 详解PyQt5 GUI 接收UDP数据并动态绘图的过程(多线程间信号传递)

    这篇文章主要介绍了PyQt5 GUI 接收UDP数据并动态绘图(多线程间信号传递),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-08