高并发流水号的设计与实现

1 篇文章 0 订阅
开发中经常需要一些流水号,作为编码保存在数据库中。通常有两种做法: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操作后,另一个线程才能接着来取。当每次取来的版本号到达上限的时候,就要到数据库中再取号,此时挂上写锁,后面来的线程就等着(进来了也没号给你)。取到号后,释放写锁,后面的读锁就可以继续操作。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值