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