情景展示:
银行两操作员同时操作同一账户就是典型的例子。
比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交。最后实际账户余额为1000-50=950元,但本该为1000+100-50=1050。这就是典型的并发问题。这样,就避免了操作员B用基于version=1的旧数据修改的结果覆盖操作员A的操作结果的可能。
示例代码:
account建库脚本
drop table if exists account_wallet;
/*==============================================================*/
/* Table: account_wallet */
/*==============================================================*/
create table account_wallet
(
id int not null comment '用户钱包主键',
user_open_id varchar(64) comment '用户中心的用户唯一编号',
user_amount decimal(10,5),
create_time datetime,
update_time datetime,
pay_password varchar(64),
is_open int comment '0:代表未开启支付密码,1:代表开发支付密码',
check_key varchar(64) comment '平台进行用户余额更改时,首先效验key值,否则无法进行用户余额更改操作',
version int comment '基于mysql乐观锁,解决并发访问'
primary key (id)
);
dao层
AccountWallet selectByOpenId(String openId);
int updateAccountWallet(AccountWallet record);
service 层
AccountWallet selectByOpenId(String openId);
int updateAccountWallet(AccountWallet record);
serviceImpl层
public AccountWallet selectByOpenId(String openId) {
// TODO Auto-generated method stub
return accountWalletMapper.selectByOpenId(openId);
}
public int updateAccountWallet(AccountWallet record) {
// TODO Auto-generated method stub
return accountWalletMapper.updateAccountWallet(record);
}
sql.xml
controller 层
package com.settlement.controller;
import java.math.BigDecimal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.settlement.commons.base.BaseController;
import com.settlement.model.AccountWallet;
import com.settlement.service.AccountWalletService;
import com.taobao.api.internal.util.StringUtils;
/**
* 用户钱包Controller
*
* @author zzg
* @date 2017-02-10
*/
@Controller
@RequestMapping(value = "/wallet")
public class WalletController extends BaseController {
@Autowired
private AccountWalletService accountWalletService;
/**
* 针对业务系统高并发-----修改用户钱包数据余额,采用乐观锁
*
* @return
*/
@RequestMapping(value = "/walleroptimisticlock.action", method = RequestMethod.POST)
@ResponseBody
public String walleroptimisticlock(HttpServletRequest request) {
String result = "";
try {
String openId = request.getParameter("openId") == null ? null
: request.getParameter("openId").trim(); // 用户唯一编号
String openType = request.getParameter("openType") == null ? null
: request.getParameter("openType").trim(); // 1:代表增加,2:代表减少
String amount = request.getParameter("amount") == null ? null
: request.getParameter("amount").trim(); // 金额
if (StringUtils.isEmpty(openId)) {
return "openId is null";
}
if (StringUtils.isEmpty(openType)) {
return "openType is null";
}
if (StringUtils.isEmpty(amount)) {
return "amount is null";
}
AccountWallet wallet = accountWalletService.selectByOpenId(openId);
// 用户操作金额
BigDecimal cash = BigDecimal.valueOf(Double.parseDouble(amount));
cash.doubleValue();
cash.floatValue();
if (Integer.parseInt(openType) == 1) {
wallet.setUserAmount(wallet.getUserAmount().add(cash));
} else if (Integer.parseInt(openType) == 2) {
wallet.setUserAmount(wallet.getUserAmount().subtract(cash));
}
int target = accountWalletService.updateAccountWallet(wallet);
System.out.println("修改用户金额是否:" + (target == 1 ? "成功" : "失败"));
} catch (Exception e) {
result = e.getMessage();
return result;
}
return "success";
}
}
package com.settlement.concurrent;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import com.settlement.commons.utils.HttpRequest;
/**
* 模拟用户的并发请求,检测用户乐观锁的性能问题
*
* @author zzg
* @date 2017-02-10
*/
public class ConcurrentTest {
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args){
CountDownLatch latch=new CountDownLatch(1);//模拟5人并发请求,用户钱包
for(int i=0;i<5;i++){//模拟5个用户
AnalogUser analogUser = new AnalogUser("user"+i,"58899dcd-46b0-4b16-82df-bdfd0d953bfb","1","20.024",latch);
analogUser.start();
}
latch.countDown();//计数器減一 所有线程释放 并发访问。
System.out.println("所有模拟请求结束 at "+sdf.format(new Date()));
}
static class AnalogUser extends Thread{
String workerName;//模拟用户姓名
String openId;
String openType;
String amount;
CountDownLatch latch;
public AnalogUser(String workerName, String openId, String openType, String amount,
CountDownLatch latch) {
super();
this.workerName = workerName;
this.openId = openId;
this.openType = openType;
this.amount = amount;
this.latch = latch;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
latch.await(); //一直阻塞当前线程,直到计时器的值为0
} catch (InterruptedException e) {
e.printStackTrace();
}
post();//发送post 请求
}
public void post(){
String result = "";
System.out.println("模拟用户: "+workerName+" 开始发送模拟请求 at "+sdf.format(new Date()));
result = HttpRequest.sendPost("http://localhost:8080/Settlement/wallet/walleroptimisticlock.action", "openId="+openId+"&openType="+openType+"&amount="+amount);
System.out.println("操作结果:"+result);
System.out.println("模拟用户: "+workerName+" 模拟请求结束 at "+sdf.format(new Date()));
}
}
}