1、 最简单的,页面的《提交》按钮,在点击后(校验通过后)Disabled,这样用户就不会重复点击《提交》按钮;
2、 数据库表增加唯一性索引(比如memberId),这个只能解决一般(为什么一般,继续看)并发插入的问题;
如果两条一样的数据插入,只有一条会成功,另外一次插入失败;
3、 使用乐观锁,在Table里增加一个version int字段,这个可以保证更新操作的并发问题;
Select version, .. from table1 where id=?
//do biz
Update set version=version+1 where id=? And version=?
在代码里,需要判断update返回的更新条数(count ==1?true:false)来判断本次更新是否成功
4、 使用悲观锁,一般使用数据库提供的功能(不建议);
select … for update(悲观锁)
//do biz
Update (事物提交,锁释放)
5、 有时候,数据库不能加唯一性索引(比如由memberId,status两个字段,业务要求memberId,status=approved的数据唯一,其他状态的数据不做限制);
这个时候,需要寻求新的协调中心(之前是数据库光荣的承担这一角色),我们现在使用的Memcached可以完成这个任务,利用memcached协议规定add的原子性,详细请点击这里
我们在信联项目里使用了这个方案,有一个缺点,就是需要侵入到业务代码里来控制并发,参考代码:
方式1-回调(推荐): try { concurrentTemplate.execute(key, new ConcurrentCallback() { public Object doInConcurrent() { // do your biz return null; } }); } catch (ConcurrentException e) { // catch this exception // return value; } 方式二:自助获取和释放锁 ConcurrentLock lock = null; try { lock = concurrentTemplate.acquireLock(key); // do your biz } catch (ConcurrentException e) { // catch this exception // return value; } finally { concurrentTemplate.releaseLock(lock); } |
ConcurrentTemplate实现代码,很简单
public class ConcurrentTemplate implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(ConcurrentTemplate.class);
private static final Boolean DEFAULT_VALUE = Boolean.TRUE;
private CacheTemplate cacheTemplate;
public void afterPropertiesSet() throws Exception {
if (cacheTemplate== null) throw new IllegalArgumentException("cacheTemplateis null.");
}
/**
* 执行具体的并发控制,需要实现ConcurrentCallback接口
*
* @param key
* @param ccb
* @return Object
* @throws ConcurrentException
*/
public Object execute(String key, ConcurrentCallback ccb) throws ConcurrentException {
ConcurrentLock lock = null;
try {
// 默认值为true,防止Counter出错引起业务不可继续执行
lock = acquireLock(key);
return ccb.doInConcurrent();
} finally {
releaseLock(lock);
}
}
/**
* 获取一把锁,需要捕获异常
*
* @param key 锁的粒度,由业务来定义
* @throws ConcurrentException 发生并发,抛异常
* @see releaseLock
*/
public ConcurrentLock acquireLock(String key) throws ConcurrentException {
// 默认值为true,防止Counter出错引起业务不可继续执行
boolean flag = true;
ConcurrentLock lock = null;
try {
flag = cacheTemplate.add(key, DEFAULT_VALUE);
} catch (StoreException e) {
log.error("key:" + key, e);
}
if (!flag) throw new ConcurrentException();
lock = new ConcurrentLock();
lock.setKey(key);
return lock;
}
/**
* 释放锁,一般要放在finally里操作
*
* @param key 锁的粒度
*/
public void releaseLock(ConcurrentLock lock) {
if (lock == null) return;
releaseLock(lock.getKey());
}
/**
* 释放锁,一般要放在finally里操作
*
* @param key 锁的粒度
*/
private void releaseLock(String key) {
try {
cacheTemplate.remove(key);
} catch (StoreException e) {
log.error("key:" + key, e); }
}
}