开发中经常需要一些流水号,作为编码保存在数据库中。通常有两种做法:1 在当前业务表上取编码的最大值,然后加一。2 建立一张保存流水号的配置表,保存当前编码的最大值。
存在的问题:方法1,当有多个线程同时取最大值时,则可能取到同一个数;或者第一个线程取到号后还没有保存,另一个线程也来取号,取到的也是同一个数,就会出现重号。如果对整张表加锁,会影响效率和并发性。方法2,多个线程同时访问时,也会出现取到同一个数的情况,但是这时候可以锁住行数据,效率比方法1高很多。本文接下来就是按照方法2进行设计和实现。
create or replace function getNum(v_type varchar)
return number
is
v_num number(15);
begin
select num into v_num from t_cfg_num where type=v_type for update;
update num set num=num+1 where type=v_type;
return v_num;
end;
在oracle中for update会锁住记录,在此次事务为提交时,其他事务不能访问该数据,从而保证线程安全。
这样在程序中调用函数getNum得到的流水号都是唯一的。
但如果并发量非常大的情况下,就可以考虑改进方法。上面的实现每次只取1个流水号,大并发时可以考虑每次取100个、1000个,然后在内存中控制并发。
package com.xushc.mybatis.version;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.xushc.mybatis.util.Util;
public class Version {
private String btype;
private long maxVal;
private int step;
private AtomicLong currVal;
private ReentrantReadWriteLock lock = null;
/**
* 构造方法
*/
public Version(String btype){
this.btype = btype;
this.maxVal = 0l;
this.currVal = new AtomicLong(0);
this.lock = new ReentrantReadWriteLock();
}
/**
*
* 获取版本
*
* @return 版本号
*/
public String getVersion() {
String version = "";
try {
// 共享读锁
lock.readLock().lock();
if (checkVal()) {
version = String.valueOf(currVal.getAndAdd(1));
}else {
lock.readLock().unlock();
// 排它写锁
lock.writeLock().lock();
try {
version = getVersionFromDB();
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.writeLock().unlock();
}
lock.readLock().lock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
return version;
}
/**
*
* 检查版本号是否可用
*
* @return 成功或者失败
*/
private boolean checkVal() {
return maxVal > currVal.get();
}
/**
* 从数据库中取出可用版本号
*/
private String getVersionFromDB() {
Long dbVersion = Util.getVersionFromDB(this.btype);
// 设置当前值
currVal.set(dbVersion);
step = 10;
maxVal = dbVersion + step;
return String.valueOf(currVal.getAndAdd(1));
}
}
上面的代码中,通过getVersion方法获取流水号,进入时去读锁(如果其他线程也在读,则可以取到读锁;如果有其他线程有写锁,则等待写锁的线程结束,才能获得读锁),Atomic是java中的原子类,保证每个线程取时数据的原子性,就是线程安全,一个线程加1操作后,另一个线程才能接着来取。当每次取来的版本号到达上限的时候,就要到数据库中再取号,此时挂上写锁,后面来的线程就等着(进来了也没号给你)。取到号后,释放写锁,后面的读锁就可以继续操作。