synchronized(java高併發下的唯一性驗證)
阿新 • • 發佈:2018-12-30
一般會遇到唯一性的問題,比如新增使用者要求使用者名稱稱或登陸名唯一,我們通常的做法是:先根據條件去資料中查詢是否存在,如果存在則提示已經存在了,不允許新增,否則插入。
但是這種做法在兩種情況下很容易出現問題:
1. 當新增使用者這個過程耗時比較長時,如果兩個人同時添加了一個相同名稱的使用者,低併發的情況下容易兩個驗證都通過。比如:第一個新增還沒有進入資料庫,第二個已經通過了驗證,準備執行新增操作時。
2.當新增使用者這個過程耗時較短,但是是高併發的情況下,很容易新增多個相同的使用者。
查找了網上多個資料後發現以下幾種方法和實驗過程是比較有價值的,如下:
下面是一個簡單的例子, 向表t_test_curr插入資料,t_test_curr表包含兩個欄位,一個id(主鍵,自增長),一個username,要求唯一
1 .不考慮併發性的做法:
public void testConcurr (String username) { //t_test_curr併發測試表名 String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString(); List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username}); if (uniqueList != null && uniqueList.size() > 0) { throw new OperateFailureException("使用者名稱重複!"); } String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString(); this.baseDao.executeDdlSql(saveSql, new Object[]{username}); }
測試程式碼:
class TestConThread implements Runnable { private ActivityService activityService; TestConThread (ActivityService activityService) { this.activityService = activityService; } @Override public void run () { activityService.testConcurr("malone"); } } public static void main (String[] args) throws Exception{ ApplicationContext ctx = BaseTest.getCtx(); ActivityService activityService = (ActivityService)ctx.getBean("activityService"); TestConThread testConThread = new TestConThread(activityService); Thread[] threads = new Thread[10]; long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { threads[i] = new Thread(testConThread); threads[i].start(); } for (int i = 0; i < 10; i++) { threads[i].join(); } long end = System.currentTimeMillis(); System.out.println(end - start); }
用上面的程式碼測試,啟動十個執行緒,資料庫中插入了9條記錄,所以這種程式碼在併發的情況下沒有任何抵抗能力,程式碼中打印出來的結果為:2582
2 使用鎖,鎖上當前方法:
public void testConcurr2 (String username) {
lock.lock();
try {
String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString();
List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username});
if (uniqueList != null && uniqueList.size() > 0) {
throw new OperateFailureException("使用者名稱重複!");
}
String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString();
this.baseDao.executeDdlSql(saveSql, new Object[]{username});
} finally {
lock.unlock();
}
}
上面的執行緒類的run方法呼叫當前方法,這樣資料不會重複,只會有一條,但是用鎖會對吞吐量造成一定的影響,程式碼列印結果:3683,從列印結果來看,基本上慢了1/3
3 使用併發集合:
public void testConcurr1 (String username) {
if (isInCache(username)) {
throw new OperateFailureException("你輸入的使用者名稱已經存在!");
}
String uniqueSql = new StringBuilder("select 1 from t_test_curr where username = ?").toString();
List<SerializableJSONObject> uniqueList = this.baseDao.sqlQueryResult4Cache(uniqueSql, new Object[]{username});
if (uniqueList != null && uniqueList.size() > 0) {
throw new OperateFailureException("使用者名稱重複!");
}
String saveSql = new StringBuilder("insert into t_test_curr(username) values (?)").toString();
this.baseDao.executeDdlSql(saveSql, new Object[]{username});
removeFromCache(username);
}
public boolean isInCache (String username) {
if (map.containsKey(username)) {
return true;
} else {
map.put(username, username);
return false;
}
}
public void removeFromCache (String username) {
map.remove(username);
}
上面的程式碼邏輯為:在service中放一個ConcurrentHashMap,當請求來時,就把username和map裡的資料比較,如果重複,就直接提示重複,如果不重複就放入的集合中,然後在驗證在資料庫中是否重複,如果重複則提示,不重複則插入,並移除併發集合中的username;這樣做的好處時,不會鎖住程式碼,系統吞吐量不會受影響,而且在併發環境下不會出現重複資料,測試程式碼列印結果:2459