一、乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:
1 |
|
CAS操作方式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。
代码示例:
public RefundOrderInfo saveRefund(PayOrderInfo payOrderInfo, FundRefundReq req) {
try {
PayOrderInfo updatePayOrderInfo = new PayOrderInfo();
updatePayOrderInfo.setOrdNo(payOrderInfo.getOrdNo());
//set新的状态R1(期望更新为的状态,预期值)
updatePayOrderInfo.setOrdSts(PayOrderInfoStsEnum.R1.getStatus());
updatePayOrderInfo.setMsgInfo(PayOrderInfoStsEnum.R1.getDesc());
RefundOrderInfo refundOrderInfo = builderRefundOrdInfo(payOrderInfo, req);
refundTransactionService.updatePayOrdInfoAndCreateRefund(updatePayOrderInfo, PayOrderInfoStsEnum.S, refundOrderInfo);
return refundOrderInfo;
} catch (DuplicateKeyException de) {
log.error("重复入库,rfdOrderId={},mercOrNo={}", req.getRfdOrderId(), req.getMercOrdNo(), de);
throw new PFDException(RspCodeEnum.REPEATE_REQUEST);
} catch (Exception e) {
log.error("saveRefund 异常,rfdOrderId={},mercOrNo={}", req.getRfdOrderId(), req.getMercOrdNo(), e);
throw e;
}
}
//被调用的updatePayOrdInfoAndCreateRefund方法:
public void updatePayOrdInfoAndCreateRefund(PayOrderInfo payOrderInfo, PayOrderInfoStsEnum fromSts, RefundOrderInfo refundOrderInfo) {
//fromSts.getStatus():S(内存值) payOrderInfo.getOrdSts():R1 (新值)
int result = payOrderInfoMapper.updateStatusFromOldStatus(payOrderInfo.getOrdNo(), fromSts.getStatus(), payOrderInfo.getOrdSts(), payOrderInfo.getMsgInfo());
if (result == 0) {
throw new UnexpectedRollbackException("PayOrderInfo更新表异常");
}
result = refundOrderInfoMapper.insertSelective(refundOrderInfo);
if (result == 0) {
throw new UnexpectedRollbackException("refundOrderInfo表入库异常");
}
}
//被调用的SQL方法
int updateStatusFromOldStatus(@Param("ordNo") String ordNo, @Param("oldOrdSts") String oldOrdSts, @Param("newOrdSts") String newOrdSts, @Param("msgInfo") String msgInfo);
//具体SQL
UPDATE refund_order_info SET
msg_info = #{desc},
rfd_sts=#{newOrdSts}
WHERE rfd_ord_no=#{rfdOrdNo} AND rfd_sts = #{oldSts}
总是假设最坏的情况,每次取数据时都认为其他线程会去修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起等待。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁;
排他性,只有当前进行加锁的用户,才可以对被锁的数据进行操作,账务中悲观锁的使用相对较多,其他系统使用较少,因为悲观锁影响性能。
代码示例:
List selectByAcNoForUpdate(@Param("acNo") String acNo, @Param("acOrg") String acOrg);
//具体SQL,通过select xxx for update进行加锁
List acmtacblList = acmtacblMapper.selectByAcNoForUpdate(acNo, acOrg);
boolean isClose = true;
String closeSts = acmtacin.getAcSts();
//对锁住的数据acmtacblList进行操作,一般是当事务释放时,锁被释放;
if(acmtacblList.size() > 0){
String capTypSts = "3";
for(int i = 0; i
比如redis锁:通过查询获取key的方式实现加锁,可以理解为是悲观锁的一种;
每次对数据进行操作,先查询key,如果查询到key,即当前资源被占用,不能进行操作;如果要加锁的那个锁key不存在的话,你就可以进行加锁;
执行lock.unlock(),就可以释放分布式锁;
代码示例:
if (!redisService.lockDefaultTime(payOrderInfo.getMercId(), payOrderInfo.getOrdNo())) {
log.warn("获取锁失败返回当时订单状态,ordNo={}", payOrderInfo.getOrdNo());
this.getRspByDB(payOrderInfo.getOrdNo(), fundPurchaseRsp);
return;
}
//调用lock方法进行锁处理
public boolean lockDefaultTime(String mercId, String mercOrderId) {
return lock(mercId, mercOrderId, ExpireTimeConfig.DEFAULT_LOCK_SECONDS);
}
lock实现:
//加锁的具体实现
public boolean lock(String mercId, String mercOrderId, int expireSeconds) {
String lockKey = getLockKey(mercId, mercOrderId);
try {
for (int i = 0; i < RETRY_COUNT; i++) { // 3次重试
// value 设置为到期时间
String value = String.valueOf(System.currentTimeMillis() + expireSeconds * 1000L);
boolean ret = this.setnx(lockKey, value);
if (ret == false) {
// 死锁检测 ------------------------------- begin
String lockKeyValue = this.getStr(lockKey);
// Case1: 锁到期了,但没释放。
if (lockKeyValue != null) {
// Case1-STEP1: 比较现在V.S.锁的到期时间
long now = System.currentTimeMillis();
long oldLockTimeOut = Long.parseLong(lockKeyValue);
long newLockTimeOut = System.currentTimeMillis() + expireSeconds * 1000L;
// Case1-STEP2: 比较锁是否已经失效
if (now > oldLockTimeOut + 1 * 1000L) { // 1秒的delay factor
log.error("死锁检测-检测到锁已经失效!");
// Case1-STEP3: 获取上一个锁的到期时间,并设置现在的锁到期时间。
String oldTimeOutFromRedis = this.getAndSet(lockKey, String.valueOf(newLockTimeOut));
if (StringUtils.equals(String.valueOf(oldLockTimeOut), oldTimeOutFromRedis)) {
// Case1-STEP4: 获取成功,设置失效时间.
this.setKeyExpire(lockKey, expireSeconds);
log.info("获取锁成功-死锁检测生效");
return true;
}
}
} else {
Thread.sleep(50);
continue;
}
// 死锁检测 ------------------------------- end
log.info("获取锁-失败");
return ret;
} else {
this.setKeyExpire(lockKey, expireSeconds);
return true;
}
}
} catch (Exception ex) {
log.error("异常-redis设置锁异常", ex);
return false;
}
return false;
}
释放锁的实现:
//调用unLock进行锁释放
redisService.unLock(payOrderInfo.getMercId(), payOrderInfo.getOrdNo());
//unLock释放锁的实现
public void unLock(String mercId, String mercOrderId) {
String lockKey = getLockKey(mercId, mercOrderId);
try {
this.delete(lockKey);
} catch (Exception ex) {
log.error("异常-释放锁异常, mercId={}, mercOrderId={}", mercId, mercOrderId, ex);
}
}