多线程,内存存放数据检验
Posted hyiam
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程,内存存放数据检验相关的知识,希望对你有一定的参考价值。
背景:为提高帐户校验效率,将原来的数据库检验方式变更为将数据存于内存,动态变更内存数据,检验取内存数据的方式,解决检验问题
方案:
1. 创建对象Map用于存放帐户当前可用金额信息
2. 检验时,判断金额信息是否存在,存在则使用内存金额进行校验,不存在则查询数据库获取最新的可用金额信息
3. 检验通过,则更新可用金额
4.每隔一段时间,对内存消息进行核对,将数据库查询到的信息更新到内存中
关键点实现:
1. 存放帐户可用金额的对用,必须是线程安全的,使用ConcurrentHashMap,可以在保证效率的同时做到线程安全。
eg:
// 账户可用额度相关信息Map public static ConcurrentHashMap<Long,AccountGroupInfo> currentAccountGroupAmountMap = new ConcurrentHashMap<Long,AccountGroupInfo>();
2. 在对帐户可用额度Map进行操作时,需要进行加锁,保证多线程调用时的数据正确性,同时为了满足加锁未成功的情况下,可设置等待时间,不让校验操作无限等待。
在Map中设计isLock字段,标志帐户是被锁,进行校验,更新操作时,需要对帐户进行加锁。
3. 在进行加锁操作时,需要进行同步处理,保证锁的获取是同步的。使用synchronized字段实现锁,但是只对单个的帐户对象进行加锁,保证在同一个帐户的加锁操作上是同步的。
4. 对加锁操作进行尝试次数和等待时间控制,加锁操作可以尝试5次,每次尝试失败等待100毫秒,如果在5次100毫秒内未加锁成功,则抛出异常,将此次的校验置为失败,进行下次操作
eg:
synchronized (accountGroupInfo) { boolean lockSuccess = false; for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"尝试加锁次数"+(i+1)+"==========laccountid============"+laccountid); if(!accountGroupInfo.isLock()){ accountGroupInfo.setLock(true); lockSuccess = true; System.out.println("---------------加锁成功"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); break; }else{ Thread.sleep(100); } } if(!lockSuccess){ System.out.println("加锁失败"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); throw new Exception("加锁失败"); } }
附:
1.对象加锁
String key = getKey(); Object object = getLock(key); synchronized (object ) { // 逻辑处理代码 } /** * 得到对象锁 */ private static Object getLock(Long key){ Object value = new Object(); Object v = locks.putIfAbsent(key, value); if(v == null){ return value; } return v; }
2. 锁逻辑原则
3. 整体代码实现
package com.iss.itreasury.accountgroup; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.Timestamp; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.concurrent.ConcurrentHashMap; import com.iss.itreasury.settlement.base.SettlementException; import com.iss.itreasury.settlement.setting.dao.SettAccountGroupSettingDao; import com.iss.itreasury.settlement.util.UtilOperation; import com.iss.itreasury.util.DataFormat; import com.iss.itreasury.util.Database; import com.iss.itreasury.util.Env; public class AccountGroupOperation { // 账户与账户组的关联关系 public static ConcurrentHashMap<Long,Long> accountGroup = new ConcurrentHashMap<Long,Long>(); // 账户组可用额度相关信息Map public static ConcurrentHashMap<Long,AccountGroupInfo> currentAccountGroupAmountMap = new ConcurrentHashMap<Long,AccountGroupInfo>(); // 帐户组锁 public static ConcurrentHashMap<Long, Object> locks = new ConcurrentHashMap<Long, Object>(); /** * 判断该账户所在账户组本区间是否透支 * @param 账户ID,支付金额 * @return 透支true 不透支false * @throws Exception * */ public static boolean isOverDraftAccgroup(long laccountid,double payamount,long lOfficeID,long lCurrencyID) throws Exception{ return isOverDraftAccgroup(laccountid, payamount, lOfficeID, lCurrencyID,false); } /** * 判断该账户所在账户组本区间是否透支 * @param 账户ID,支付金额 * @return 透支true 不透支false * @throws Exception * */ public static boolean isOverDraftAccgroup(long laccountid,double payamount,long lOfficeID,long lCurrencyID,boolean isEbank) throws Exception{ boolean flag = false; // 开机日期 Timestamp systemDate = Env.getSystemDate(lOfficeID, lCurrencyID); SettAccountGroupSettingDao dao = new SettAccountGroupSettingDao(); // 1. 帐户是否加入了帐户组 long accountGroupId; // 如果账户与帐户组关联关系没有,进行初始化 if(isEbank){ accountGroup = new ConcurrentHashMap<Long,Long>(); } if(accountGroup.size()==0){ AccountGroupOperation.init(); } try { // 1.1 获取帐户组ID accountGroupId = findAccountGroup(laccountid); } catch (Exception e1) { e1.printStackTrace(); throw new Exception("获取帐户组关系失败"); } // 1.2 帐户不在帐户组中,无需校验 if(accountGroupId == -1){ System.out.println("帐户:"+laccountid+"不在帐户组中,无需校验"); return false; } Object lock = getLock(accountGroupId); // 2. 内存currentAccountGroupAmountMap中是否存在该账户组可用额度相关信息 synchronized (lock) { if(currentAccountGroupAmountMap.get(accountGroupId)==null||isEbank){ System.out.println("==================账户组可用额度Map初始化=================="); // 2.1 账户组额度信息初始化 AccountGroupInfo accountGroupInfo = new AccountGroupInfo(); accountGroupInfo.setLock(false); accountGroupInfo.setLimitAmount(-1); accountGroupInfo.setLimitDate(systemDate); boolean isAccgroupBudget = false; try { // 账户是否加入账户组且该年月设置了账户组额度 isAccgroupBudget = dao.CheckAccgroupExistBudget(laccountid, accountGroupInfo.getLimitDate()) > 0; } catch (Exception e) { e.printStackTrace(); throw new Exception("账户是否加入账户组且该年月设置了账户组额度校验失败"); } accountGroupInfo.setAccgroupBudget(isAccgroupBudget); currentAccountGroupAmountMap.put(accountGroupId, accountGroupInfo); } } AccountGroupInfo accountGroupInfo = currentAccountGroupAmountMap.get(accountGroupId); // 3 额度校验 是否设置了预算 // 如果月份发生改变,对是否设置预算进行重新赋值 if(!(DataFormat.getRealMonthString(accountGroupInfo.getLimitDate()).equals(DataFormat.getRealMonthString(systemDate))&&DataFormat.getYearString(accountGroupInfo.getLimitDate()).equals(DataFormat.getYearString(systemDate)))){ boolean isAccgroupBudget = dao.CheckAccgroupExistBudget(laccountid, accountGroupInfo.getLimitDate()) > 0; accountGroupInfo.setAccgroupBudget(isAccgroupBudget); // 有变成无,移除缓存内容 if(accountGroupInfo.isAccgroupBudget()&&!isAccgroupBudget){ currentAccountGroupAmountMap.remove(accountGroupId); System.out.println("帐户:"+laccountid+"不需要进行账户组额度校验"); return false; } } if(!accountGroupInfo.isAccgroupBudget()){ System.out.println("帐户:"+laccountid+"不需要进行账户组额度校验"); return false; } // 4.帐户组加锁 synchronized (accountGroupInfo) { boolean lockSuccess = false; for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"尝试加锁次数"+(i+1)+"==========laccountid============"+laccountid); if(!accountGroupInfo.isLock()){ accountGroupInfo.setLock(true); lockSuccess = true; System.out.println("---------------加锁成功"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); break; }else{ Thread.sleep(1000); } } if(!lockSuccess){ System.out.println("加锁失败"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); throw new Exception("加锁失败"); } } // 4.2 额度校验 额度核对(如果“当前可用额度”为-1或“额度日期”不等于开机日或“更新时间”与当前应用服务器时间差值超过1个小时,则执行核对逻辑) try{ checkAccountGroup(accountGroupId,lOfficeID,lCurrencyID); }catch (Exception e) { e.printStackTrace(); if(accountGroupInfo.isLock()){ accountGroupInfo.setLock(false); } throw new Exception("帐户组额度核对失败"); } // 4.3 额度校验 System.out.println("======占用前======"); System.out.println("账户ID======"+laccountid); System.out.println("账户组ID======"+accountGroupId); System.out.println("账户组可用金额======"+DataFormat.formatNumber(accountGroupInfo.getLimitAmount(),2)); if(UtilOperation.Arith.sub(payamount, accountGroupInfo.getLimitAmount()) > 0 ){ if(accountGroupInfo.isLock()){ accountGroupInfo.setLock(false); } System.out.println("帐户组透支---------------解锁成功"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); return true; } // 5 额度占用 accountGroupInfo.setLimitAmount(UtilOperation.Arith.sub(accountGroupInfo.getLimitAmount(), payamount)); accountGroupInfo.setUpdateTime(new Timestamp(System.currentTimeMillis())); System.out.println("======占用后======"); System.out.println("账户ID======"+laccountid); System.out.println("账户组ID======"+accountGroupId); System.out.println("账户组可用金额======"+DataFormat.formatNumber(accountGroupInfo.getLimitAmount(),2)); // 6 帐户组解锁 if(accountGroupInfo.isLock()){ accountGroupInfo.setLock(false); } System.out.println("---------------解锁成功"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); return flag; } /** * 释放账户组额度 * @param 账户ID,释放金额 * @throws Exception * */ public static void releaseDraftAccgroup(long laccountid,double payamount) throws Exception{ // 1. 帐户是否加入了帐户组 long accountGroupId; try { // 1.1 获取帐户组ID accountGroupId = findAccountGroup(laccountid); } catch (Exception e1) { e1.printStackTrace(); throw new Exception("获取帐户组关系失败"); } // 1.2 帐户不在帐户组中,无需校验 if(accountGroupId == -1){ System.out.println("帐户:"+laccountid+"不在帐户组中"); return; } AccountGroupInfo accountGroupInfo = currentAccountGroupAmountMap.get(accountGroupId); // 没有缓存无需释放 if(accountGroupInfo==null){ System.out.println("账户组:"+accountGroupId+"没有缓存无需释放"); return; } // 2 额度校验 是否设置了预算 if(!accountGroupInfo.isAccgroupBudget()){ System.out.println("账户组:"+accountGroupId+"没有设置预算"); return; } // 3 帐户组加锁 Object lock = getLock(accountGroupId); boolean lockSuccess = false; synchronized (lock) { for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"尝试加锁次数"+(i+1)+"==========laccountid============"+laccountid); if(!accountGroupInfo.isLock()){ accountGroupInfo.setLock(true); lockSuccess = true; System.out.println("---------------加锁成功"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); break; }else{ Thread.sleep(1000); } } if(!lockSuccess){ System.out.println("加锁失败"+Thread.currentThread().getName()+"==========laccountid============"+laccountid); throw new Exception("加锁失败"); } } System.out.println("======释放前======"); System.out.println("账户ID======"+laccountid); System.out.println("账户组ID======"+accountGroupId); System.out.println("账户组可用金额======"+DataFormat.formatNumber(accountGroupInfo.getLimitAmount(),2)); // 4 可用额度释放 accountGroupInfo.setLimitAmount(UtilOperation.Arith.add(accountGroupInfo.getLimitAmount(), payamount)); System.out.println("======释放后======"); System.out.println("账户ID======"+laccountid); System.out.println("账户组ID======"+accountGroupId); System.out.println("账户组可用金额======"+DataFormat.formatNumber(accountGroupInfo.getLimitAmount(),2)); // 5 帐户组解锁 if(accountGroupInfo.isLock()){ accountGroupInfo.setLock(false); } } /** * 帐户组更新方法 * @param 账户ID,支付金额 * @return 透支true 不透支false * @throws Exception * */ public static void DraftAccgroupUpdate(long accountGroupId,String year,String month,long lOfficeID,long lCurrencyID) throws Exception{ // 开机日期 Timestamp systemDate = Env.getSystemDate(lOfficeID, lCurrencyID); SettAccountGroupSettingDao dao = new SettAccountGroupSettingDao(); // 如果预算不为当前月份 if(!(DataFormat.getRealMonthString(systemDate).equals(month)&&DataFormat.getYearString(systemDate).equals(year))){ // 无需通知 System.out.println("==================预算不为当前月份无需通知=================="); return; } Object lock = getLock(accountGroupId); // 2. 内存currentAccountGroupAmountMap中是否存在该账户组可用额度相关信息 synchronized (lock) { if(currentAccountGroupAmountMap.get(accountGroupId)==null){ System.out.println("==================账户组可用额度Map初始化=================="); // 2.1 账户组额度信息初始化 AccountGroupInfo accountGroupInfo = new AccountGroupInfo(); accountGroupInfo.setLock(false); accountGroupInfo.setLimitAmount(-1); accountGroupInfo.setLimitDate(systemDate); boolean isAccgroupBudget = false; try { // 账户是否加入账户组且该年月设置了账户组额度 isAccgroupBudget = dao.CheckGroupExistBudget(accountGroupId, accountGroupInfo.getLimitDate()) > 0; } catch (Exception e) { e.printStackTrace(); throw new Exception("账户是否加入账户组且该年月设置了账户组额度校验失败"); } accountGroupInfo.setAccgroupBudget(isAccgroupBudget); currentAccountGroupAmountMap.put(accountGroupId, accountGroupInfo); return; } } AccountGroupInfo accountGroupInfo = currentAccountGroupAmountMap.get(accountGroupId); // 4.帐户组加锁 synchronized (accountGroupInfo) { boolean lockSuccess = false; for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"尝试加锁次数"+(i+1)+"==========laccountid============"+accountGroupId); if(!accountGroupInfo.isLock()){ accountGroupInfo.setLock(true); lockSuccess = true; System.out.println("---------------加锁成功"+Thread.currentThread().getName()+"==========laccountid============"+accountGroupId); break; }else{ Thread.sleep(1000); } } if(!lockSuccess){ System.out.println("加锁失败"+Thread.currentThread().getName()+"==========laccountid============"+accountGroupId); throw new Exception("加锁失败"); } } // 额度更新为-1 accountGroupInfo.setLimitAmount(-1); // 6 帐户组解锁 if(accountGroupInfo.isLock()){ accountGroupInfo.setLock(false); } System.out.println("---------------解锁成功"+Thread.currentThread().getName()+"==========laccountid============"+accountGroupId); } /** * 得到帐户组锁 */ private static Object getLock(Long key){ Object value = new Object(); Object v = locks.putIfAbsent(key, value); if(v == null){ return value; } return v; } /** * add by jiecheni 2017-11-02 * 账户与账户组关联关系初始化 */ public static void init() { System.out.println("==========================账户与账户组关联关系初始化开始=========================="); Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { //先清理缓存 accountGroup.clear(); con = Database.getConnection(); StringBuffer sb = new StringBuffer(); sb.append(" select a.naccgroupid,a.naccountid from sett_budgetaccassign a where a.nstatusid > 0 "); ps = con.prepareStatement(sb.toString()); rs = ps.executeQuery(); while (rs.next()) { try { accountGroup.put(rs.getLong("naccountid"), rs.getLong("naccgroupid")); } catch (NullPointerException e) { continue; } } rs.close(); rs = null; ps.close(); ps = null; con.close(); con = null; } catch (Exception e) { e.printStackTrace(); } finally { try { if (rs != null) { rs.close(); rs = null; } if (ps != null) { ps.close(); ps = null; } if (con != null) { con.close(); con = null; } } catch (Exception e) { e.printStackTrace(); } } System.out.println("==========================账户与账户组关联关系初始化结束,初始化关系数量=========================="+accountGroup.size()); } /** * add by jiecheni 2017-11-02 * 查询账户是否在账户组 * @param id * @return * @throws Exception */ public static long findAccountGroup(long id) throws Exception { Long result = null; try { result = accountGroup.get(Long.valueOf(id)); } catch (Exception e) { e.printStackTrace(); throw new Exception("查询出现异常,出错原因:"+e.getMessage(), e); } return result == null ? -1 : result; } /** * add by jiecheni 2017-11-02 * 将账户与账户组关联信息存入缓存 * @param accountId * @param accountGroupId */ public static void putAccountGroup(long accountId,long accountGroupId) { accountGroup.put(accountId, accountGroupId); } /** * add by jiecheni 2017-11-02 * 将账户与账户组关联信息从缓存中清除 * @param accountId */ public static void removeAccountGroup(long accountId) { accountGroup.remove(accountId); } /** * 账户组额度核对 * @param accountGroupId 账户组Id * @param lOfficeID 办事处Id * @param lCurrencyID 币种id * @throws Exception */ public static void checkAccountGroup(long accountGroupId,long lOfficeID,long lCurrencyID) throws Exception{ int isCheckResult = isCheckAccountGroup(accountGroupId,lOfficeID,lCurrencyID); AccountGroupInfo accountGroupInfo = currentAccountGroupAmountMap.get(accountGroupId); Timestamp limitDate = accountGroupInfo.getLimitDate(); if(isCheckResult == 3){ Timestamp systemDate = Env.getSystemDate(lOfficeID,lCurrencyID); double accountGroupAmount = UtilOperation.Arith.sub(queryAccountGroupBudget(accountGroupId,systemDate),queryAccountGroupUsedAmount(accountGroupId,systemDate)); accountGroupInfo.setLimitDate(systemDate); accountGroupInfo.setLimitAmount(accountGroupAmount); accountGroupInfo.setUpdateTime(new Timestamp(System.currentTimeMillis())); currentAccountGroupAmountMap.put(accountGroupId, accountGroupInfo); }else if(isCheckResult > 0){ double accountGroupAmount = UtilOperation.Arith.sub(queryAccountGroupBudget(accountGroupId,limitDate),queryAccountGroupUsedAmount(accountGroupId,limitDate)); accountGroupInfo.setLimitAmount(accountGroupAmount); accountGroupInfo.setUpdateTime(new Timestamp(System.currentTimeMillis())); currentAccountGroupAmountMap.put(accountGroupId, accountGroupInfo); } } /** * 是否核对账户组额度 * @param accountGroupId 账户组Id * @param lOfficeID 办事处Id * @param lCurrencyID 币种id * @return * 1: 当前可用额度为-1. * 2: 账户组更新时间与当前应用服务器时间差是否超过一个小时. * 3: 开机日是否与额度日期不同. * */ public static int isCheckAccountGroup(long accountGroupId,long lOfficeID,long lCurrencyID) throws Exception{ int isCheckAccountGroup = 0; AccountGroupInfo accountGroupInfo = currentAccountGroupAmountMap.get(accountGroupId); if(accountGroupInfo != null){ //如果"当前可用额度"为-1或"额度日期"不等于开机日或"更新时间"与当前应用服务器时间差值超过1个小时,则执行核对逻辑 //a、"当前可用额度"为-1 if(accountGroupInfo.getLimitAmount() <= 0){ return 1; } //b、账户组更新时间与当前应用服务器时间差是否超过一个小时 if(accountGroupInfo.getUpdateTime() != null){ if((System.currentTimeMillis()-accountGroupInfo.getUpdateTime().getTime()) > 1*60*60*1000){ return 2; } } //c、开机日是否与额度日期不同 Timestamp systemDate = Env.getSystemDate(lOfficeID,lCurrencyID); Timestamp limitDate = accountGroupInfo.getLimitDate(); boolean isSameDate = com.iss.itreasury.util.UtilOperation.isSameDate(systemDate,limitDate); if(!isSameDate){ return 3; } }else{ //初始化账户组额度信息 initAccountGroup(accountGroupId); } return isCheckAccountGroup; } /** * 初始化账户组额度信息 * @param accountGroupId 账户组Id * @throws Exception */ private static void initAccountGroup(long accountGroupId) throws Exception{ AccountGroupInfo accountGroupInfo = new AccountGroupInfo(); Timestamp systemDate = Env.getSystemDate(1,1); double accountGroupAmount = UtilOperation.Arith.sub(queryAccountGroupBudget(accountGroupId,systemDate),queryAccountGroupUsedAmount(accountGroupId,systemDate)); accountGroupInfo.setAccgroupBudget(true); accountGroupInfo.setLock(false); accountGroupInfo.setLimitDate(systemDate); accountGroupInfo.setLimitAmount(accountGroupAmount); accountGroupInfo.setUpdateTime(new Timestamp(System.currentTimeMillis())); currentAccountGroupAmountMap.put(accountGroupId, accountGroupInfo); } /** * 查询账户组设置预算额度 * @param accountGroupId 账户组Id * @param executedate 执行日期 * @return * @throws Exception */ public static double queryAccountGroupBudget(long accountGroupId,Timestamp executedate) throws Exception{ //账户组设置预算额度 double accountGroupBudgetAmount = 0.0; int year = 0;//年份 int month = 0;//月份 AccountGroupDao agDao = new AccountGroupDao(); if(executedate != null){ Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(executedate.getTime()); year = calendar.get(GregorianCalendar.YEAR); month = calendar.get(GregorianCalendar.MONTH) + 1; }else{ throw new SettlementException("执行日期为空",new Exception()); } //查询账户组设置预算额度 accountGroupBudgetAmount = agDao.queryAccountGroupBudgetAmount(accountGroupId,executedate,year,month); return accountGroupBudgetAmount; } /** * 统计查询账户组已使用额度 * 账户组已使用额度 = 网银、CBE使用额度 + 结算、银行流水入账使用额度 * @param accountGroupId 账户组Id * python多线程