一、 前言
最近在做网上法庭的一个比较有意思的小需求,就是通过扫二维码方式允许最多30个人同时进入庭审,但是不限制进入的是是不是庭审人员,也就是说只要扫了这个二维码并且当前案件对应的参与人数不到30那么就可以进入,始终维持一个庭审案件里面最多有30人。
阿里巴巴长期招聘Java研发工程师p6,p7,p8等上不封顶级别,有意向的可以发简历给我,注明想去的部门和工作地点:[email protected]
二、 方案研究
扫描二维码会调用后台的一个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 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 总结
推荐使用悲观锁方式。
欢迎关注微信公众号:‘技术原始积累’ 获取更多技术干货__