受限访问量问题中锁的使用

一、 前言

最近在做网上法庭的一个比较有意思的小需求,就是通过扫二维码方式允许最多30个人同时进入庭审,但是不限制进入的是是不是庭审人员,也就是说只要扫了这个二维码并且当前案件对应的参与人数不到30那么就可以进入,始终维持一个庭审案件里面最多有30人。

二、 方案研究

扫描二维码会调用后台的一个rpc,而Rpc会调用bo方法进行处理,那么下面就研究下bo里面里面怎么做。

由于需求是要控制一个庭审的人数,而扫码人肯定是并发的访问这个bo方法,首先会有两种思路使用数据库的锁或者在业务层面进行控制。

2.1 使用乐观锁来控制

case_id

count

1

0

如表每条记录case_id唯一,并且对应一个count字段用来维持进入庭审人员个数。 bo方法都有事务切面的。使用单个数据库。

  • 进入庭审
 prviate final int COUNT = 30
public boolean boEnterMethod(String enCaseid){
    
    第一步解密enCaseid获取真正caseId;
    第二步根据caseId获取记录(里面包含count)nowCount = count ;
        if( nowCount == COUNT){
             return false;
        }
  
    update 表 set count=nowCount+1 where count =nowCount and id = #id;
    Long rows = 更新语句返回行数;
    if(rows == 1){
        处理业务
              return true;
    }
        return false;
}
  • 退出庭审
public boolean boExitMethod(String enCaseid){
    
    第一步解密enCaseid获取真正caseId;
    第二步根据caseId获取记录(里面包含count)nowCount = count;
        if( nowCount == 0){
             return false;
        }
  
    update 表 set count=nowCount-1 where count =nowCount and id = #id;
    Long rows = 更新语句返回行数;
    if(rows == 1){
        处理业务
        return true;

    }
        return false
}

这种方式好处是在执行时候才进行校验不需要提前对记录进行加锁,坏处是,假如两个人同时扫描二维码,获取的nowCount=1那么只有一个能真正进入庭审,另外一个会失败,结果是他进入不了庭审。

乐观锁下有咩有办法解决那?答案是肯定的,还记得AQS里面的trylock?第一次cas失败,那好吧,我再循环一次再试试。所以改进在于可以加个循环,如下:

  • 进入庭审
public boolean boEnterMethod(String enCaseid){
    
  第一步解密enCaseid获取真正caseId;
  for(;;){
        第二步根据caseId获取记录(里面包含count)nowCount = count;
        if( nowCount == COUNT){
             return false;
        }
      
        update 表 set count=nowCount+1 where count =nowCount and id = #id;
        Long rows = 更新语句返回行数;
        if(rows == 1){
            处理业务

            return true;

        }
   }
}
  • 退出庭审
public boolean boExitMethod(String enCaseid){
    
    第一步解密enCaseid获取真正caseId;
    for(;;){
        第二步根据caseId获取记录(里面包含count)nowCount = count;
            if( nowCount == 0){
                 return false;
            }
      
        update 表 set count=nowCount-1 where count =nowCount and id = #id;
        Long rows = 更新语句返回行数;
        if(rows == 1){
            处理业务
            return true;
        }
    }
        
}

加个循环目前是为了避免当访问量不足30时候由于乐观锁竞争导致的失败,这里当当前访问量为30的时候直接返回是为了避免大量请求线程空轮造成tomcat线程池满。但是问题是可能查询数据库的频率比较高。

2.2 使用悲观锁来控制

  • 乐观锁
public boolean boEnterMethod(String enCaseid){
    
    第一步解密enCaseid获取真正caseId;
    第二步根据caseId获取记录(里面包含count)使用select * from 表 where .. for update 对本记录加锁
    nowCount = count;
    rowId = id;
    if( nowCount == COUNT){
         return false;
    }
  
    update 表 set count=nowCount+1 where  id = rowId;
    Long rows = 更新语句返回行数;
    if(rows == 1){
        处理业务
        return true;

    }
           return false;
}
  • 退出庭审
public boolean boExitMethod(String enCaseid){
    
    第二步根据caseId获取记录(里面包含count)使用select * from 表 where .. for update 对本记录加锁
    nowCount = count;
    rowId = id;
    if( nowCount == 0){
         return false;
    }
  
    update 表 set count=nowCount-1 where id = rowId;
    Long rows = 更新语句返回行数;
    if(rows == 1){
        处理业务
        return true;

    }
        return false
}

使用悲观锁方式是事先对记录加锁,其他事务访问时候需要等待,直到当前事务提交。

2.3 使用业务锁来控制

public class TestLock {

    private final int COUNT_NUM = 30;
    private final SafeIntegerCount count = new SafeIntegerCount(COUNT_NUM);

    private final static ConcurrentHashMap<String, SafeIntegerCount> caseLockMap = new ConcurrentHashMap<>();

    //初始化缓存
    public void init() {

        //select所有caseid到list
        for (String caseId:list) {
            SafeIntegerCount count = new SafeIntegerCount(COUNT_NUM);
            caseLockMap.put(caseId, count);
            
        }
    }

    public void boEnterMethod(String enCaseid) {

        对enCaseid进行解密得到caseId
        
        SafeIntegerCount count = caseLockMap.get(caseId);

        // 进入
        if (null == count) {
            return;
        }
        
        try {
            if (count.inc()) {
                // 处理业务
            }
        } catch (Exception e) {
            count.desc();
        }

    }

    public void boExitMethod(String enCaseid) {

        对enCaseid进行解密得到caseId
        
        SafeIntegerCount count = caseLockMap.get(caseId);

        // 进入
        if (null == count) {
            return;
        }
        
        try {
            if (count.desc()) {
                // 处理业务
            }
        } catch (Exception e) {
            count.inc();
        }

    }

}
public class SafeIntegerCount {

    //当前计数
    private int count = 0;

    //最大计数
    private int maxCount = 0;

    //公平独占锁
    private final ReentrantLock lock = new ReentrantLock(true);

    //构造函数设置最大值
    public SafeIntegerCount(int maxCount) {
        this.maxCount = maxCount;
    }

    //自增加一
    public Boolean inc() {

        lock.lock();
        try {
            if (count == maxCount) {
                return false;
            }
            ++count;

            return true;
        } finally {
            lock.unlock();
        }
    }

    //自减-
    public Boolean desc() {

        lock.lock();
        try {
            if(count == 0 ){
                return false;
            }
            --count;
            
            return true;
            
        } finally {
            lock.unlock();
        }
    }

}

使用ReentrantLock实现了一个可以判断上下限的计数器。眨眼看可以解决问题,但是仅仅单台机器可以正常,多台机器下会有问题,另外案件量特别大时候缓存可能占用大量内存。

2.4 总结

推荐使用悲观锁方式。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android先生

Android开发者怎么能不会写后台接口呢?

然后在src下创建三个包,一个放Servlet,一个放mysql的工具类,一个放对象;

8630
来自专栏大数据

Zzreal的大数据笔记-SparkDay04

Spark SQL SparkSQL的前身是Shark,它抛弃原有Shark的代码,汲取了Shark的一些优点,如内存列存储(In-Memory Columna...

20690
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(十)Chunk 和执行框架简介

Chunk 本质上是 Column 的集合,它负责连续的在内存中存储同一列的数据,接下来我们看看 Column 的实现。

5.7K1450
来自专栏学习力

《Java从入门到放弃》JavaSE入门篇:JDBC(入门版)

19890
来自专栏青玉伏案

iOS开发之SQLite-C语言接口规范(一)——Ready And Open Your SQLite

  为什么要搞一搞SQLite的C语言接口规范呢? 因为在做iOS开发中难免会遇到操作数据库的情况,你可以使用第三方的FMDB等,或者使用CoreData。但我...

23850
来自专栏

C++实现线程安全的单例模式

在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式。单例模式分为懒汉模式,跟饿汉模式两种。 首先给出饿汉模式的实现 template <class...

22970
来自专栏JavaEdge

Memcached的扩容源码分析

22150
来自专栏黑泽君的专栏

day29_Hibernate学习笔记_01

  Hibernate:是一个数据持久化层的ORM框架。   Object:对象,java对象,此处特指JavaBean。   Relational:关系,二维...

8020
来自专栏MasiMaro 的技术博文

IRP的同步

以WriteFile为例,一般的同步操作是调用WriteFile完成后,并不会返回,应用程序会在此处暂停,一直等到函数将数据写入文件中并正常返回,而异步操作则是...

18040
来自专栏数据库

高级盲注—floor,rand,group by报错注入

大家好,我是你们的老朋友Alex。最近一直在学习SQL注入,发现了很多很多有趣的东西。我就分享我的一篇有关floor,rand,group by报错注入的笔记吧...

28590

扫码关注云+社区

领取腾讯云代金券