環境:國土交易系統網上出價、限時競價階段同一個時間點多人出價,需要保證金額大的競買人一定能出價,並且時間在排序列表中也最大。
爲了防止幻讀在service層方法上加了synchronized還是出現了幻讀的情況
解決辦法 把synchronized加到Controller層或者大於事務邊界的調用層
原因 使用了spring aop事務,事務提交在一個service方法執行完畢後,可能事務還沒有提交,
這時候另外一個線程開始執行synchronized方法導致併發問題出現
代碼片段如下:Controller
private static Object lock=new Object();
//保存出價信息
synchronized(lock){
saveFlag=limitBidBiz.saveBusLimitBidInfo(user.getPkId(), moneyDouble, busId,cert,user.getOrgId(), getRequest().getRemoteAddr(), signMoney, tranId);
//更新當前最高價
//busTranBiz.updateCurPriceByTranId(tranId, moneyDouble);
//更新掛牌截止時間
//busTranBiz.updateCurEtime(tranId, TimeHelper.addMinute(busTran.getTranEtime(), Constants.ASK_PERIOD));
if(saveFlag){
super.operateLog(Constants.LOG_TYPE_PAY,LogHelper.getLoginContent(busTran.getPkId(),moneyString,Constants.RES_LIMIT));
//有人出價,時間推移5分鐘
busTranBiz.updateCurEtime(tranId, TimeHelper.addMinute(TimeHelper.getCurrentTime(), Constants.ASK_PERIOD));
if(houseState.equals(Constants.IS_TRUE)){
busBidderInfoBiz.updateIsLimitOrHouse(Constants.PRICE_HOUSE, tranId, user.getPkId());
}
//目前沒有最高價限制,maxPriceConfig默認爲0,moneyDouble爲當前出價
double maxPriceConfig = busListConfig.getPriceMax();
if(moneyDouble==maxPriceConfig&&maxPriceConfig>0){
//出價額度達到最高價
boolean isProArea=busTranBiz.getIsReSetPriceOrHouseConfig(tranId, "00C");
//獲取是否進行保障房競拍
if(!isProArea){
//當前價格等於最高價且不需要進入保障房競拍時直接調用成交定時任務(未加入)
busTran.setCurPrice(moneyDouble);
busResultBiz.tranSaction(busTran, getSessionUser().getPkId(), null);
}else{
//在限時競價階段增加5分鐘,該五分鐘爲詢問階段,這裏應該獲取保存在bus_tran中的tran_etime,不應該取當前時間
busTranBiz.updateCurEtime(tranId, TimeHelper.addMinute(TimeHelper.getCurrentTime(), Constants.ASK_PERIOD));
//保障房定時任務處理
schedulerService.schedule(tranId, Constants.RES_LAND_TYPE, TimeHelper.getCurrentTime(), moneyDouble, getSessionUser().getPkId(), "111");
}
}else{
//定時任務處理
schedulerService.schedule(tranId, Constants.RES_LAND_TYPE, TimeHelper.getCurrentTime(), moneyDouble, getSessionUser().getPkId(), tranStep);
}
//保存出價日誌信息
//super.operateLog(Constants.LOG_TYPE_PAY,LogHelper.getLoginContent(busTran.getPkId(),moneyString,Constants.RES_LIMIT));
//DWR消息分發
DwrService.perform(application,new String[]{"limitList",tranId});
state="succ";
}else{
super.operateLog(Constants.LOG_TYPE_PAY,LogHelper.getLoginContent(busTran.getPkId(),moneyString,Constants.RES_LIMIT));
//state="error|在您出價的時刻,其他競買人的出價已經達到您的出價,您的出價無效!";
state="error|在您出價的時刻,其他競買人的出價已經達到或超過您的出價,您的出價無效,若多次無法出價,請嘗試刷新頁面後再繼續出價!";
}
}
service層:
/**
* 方法描述 : 保存掛牌限時信息
* @param key
* @return
*/
@Transactional
public boolean saveBusLimitBidInfo(String accontId,Double price,String busId,String caCert,String orgId,String ip,String signPrice,String tranId){
//驗證出價時間、價格都必須大於出價列表中的數據,保證出價列表中的價格、出價時間排序正確
String bidTime = TimeHelper.getCurrentMill();
List list=jdbcDao.findForList("select PK_ID from BUS_LIMIT_BID where TRAN_ID='"+tranId+"' and BID_PRICE >= "+ price + " or BID_TIME >= '" + bidTime + "'");
UserLogBiz userLogBiz = (UserLogBiz) SpringFactoryHelp.getApplicationContext().getBean("userLogBiz");
userLogBiz.operateLog(accontId, "price:" + price + " time:" + bidTime, "",orgId,"",tranId);
if(StringHelper.isEmptyList(list)){
//出價成功,保存競價
BusLimitBid busLimit=new BusLimitBid();
busLimit.setPkId(GuidCreateHelper.getGuid());
busLimit.setAccountId(accontId);
busLimit.setBidPrice(price);
busLimit.setBidTime(bidTime);
busLimit.setBusId(busId);
busLimit.setCaCert(caCert);
busLimit.setOrgId(orgId);
busLimit.setPriceIp(ip);
busLimit.setSignPrice(signPrice);
busLimit.setTranId(tranId);
toolDao.save(busLimit);
busTranBiz.updateCurPriceByTranId(tranId, price);
return true;
}else{
//出價失敗
return false;
}
}
注意:synchronized(鎖)中的鎖線程必須使用同一個對象,如果不是同一個對象,即使加上synchronized,鎖依然沒有生效!