1. 程式人生 > >高併發流水號的設計與實現

高併發流水號的設計與實現

開發中經常需要一些流水號,作為編碼儲存在資料庫中。通常有兩種做法: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操作後,另一個執行緒才能接著來取。當每次取來的版本號到達上限的時候,就要到資料庫中再取號,此時掛上寫鎖,後面來的執行緒就等著(進來了也沒號給你)。取到號後,釋放寫鎖,後面的讀鎖就可以繼續操作。