并发加锁是怎么实现的_JAVA并发编程

并发加锁是怎么实现的_JAVA并发编程业务锁在处理并发问题时,很多情况下需要用到业务锁来达到按照某个维度同步执行业务块。例子:@Override@Transactional(rollbackFor=Exception.class,noRollbackFor=TerminateException.class)publicApplyDOsubmitApply(ApplyDOapplyDO){…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

业务锁

在处理并发问题时,很多情况下需要用到业务锁来达到按照某个维度同步执行业务块。

例子:

 

@Override
@Transactional(rollbackFor = Exception.class, noRollbackFor = TerminateException.class)
public ApplyDO submitApply(ApplyDOapplyDO) {
        LockResultEnum lockResultEnum =null;
        String lockName = new StringBuffer().append(applyDO.getSite()).append("_").append(applyDO.getSiteMemId()).toString();
        try {
            //加锁
            lockResultEnum = lockManager.getLock(lockName, LockTypeEnum.APPLY_LOCK.getCode());
            if (LockResultEnum.没有获取到锁.equals(lockResultEnum)){
                throw new BizException(ErrorCode.LOCK_FAIL);
            }
            …
            returnapplyDO;
        } catch (TerminateExceptione) {
            throwe;
        } catch (BizExceptione) {
            throw new BizException(e.getErrorCode(),e);
        } catch (Exceptione) {
            throw new BizException(ErrorCode.GENERIC_ERROR,e);
        } finally {
            //释放锁
            lockManager.releaseLock(lockName, LockTypeEnum.APPLY_LOCK.getCode(),lockResultEnum);
        }
}

 

 

 

LockManager的getLock方法实现如下:

 

@Override
public LockResultEnum getLock(StringlockName,StringlockType){
        if(StringUtil.isEmpty(lockName)){
            LOG.error("getLock()参数为空,param:" +lockName);
            throw new BizException(ErrorCode.ILLEGAL_ARGUMENT,"参数为空!");
           
        }
        //只是生成一个数据库锁名,纯粹的字符串拼接过程
        String lockName_ = getDBLockName(lockName,lockType);
        booleanisGetDbLocked =lockDao.getDbLock(lockName_);
        if (isGetDbLocked) {
            LockDO lock = lockDao.getRowLockByName(lockName);
            if (lock !=null){
                return LockResultEnum.获取锁成功;
            } else {
                return LockResultEnum.仅数据库锁;
            }
        } else {
            LOG.warn("获取锁【" +lockName_+"】失败");
            return LockResultEnum.没有获取到锁;
        }
}

 

 

 

LockManager的releaseLock方法实现如下:

 

@Override
public void releaseLock(StringlockName,StringlockType,LockResultEnumlockResultEnum) {
        String lockName_ = getDBLockName(lockName,lockType);
        if (StringUtil.isEmpty(lockName)) {
            LOG.error("releaseLock()参数为空,lockName:{}",lockName);
            throw new BizException(ErrorCode.ILLEGAL_ARGUMENT,"参数为空!");
        }
        if (LockResultEnum.获取锁成功.equals(lockResultEnum)|| LockResultEnum.仅数据库锁.equals(lockResultEnum)) {
            booleanisReleased =lockDao.releaseDbLock(lockName_);
            if (!isReleased) {
                LOG.warn("释放锁【" +lockName_+"】失败");
            }
        } else {
            LOG.debug("不需要释放锁【" +lockName_+"】");
        }
 }

 

 

 

LockDao的实现如下:

 

@Override
public boolean getDbLock(String lockCode){
        Long lock = (Long)super.getSqlMapClientTemplate().queryForObject("LockDO.getLockDbByCode",lockCode);
        booleanresult = (lock !=null&&lock.longValue()== 1) ? true:false;
        returnresult;
}
    @Override
    public boolean releaseDbLock(String lockCode) {
        Long lock = (Long)super.getSqlMapClientTemplate().queryForObject("LockDO.releaseLockDbByCode",lockCode);
        booleanresult = (lock !=null&&lock.longValue()== 1) ? true:false;
        returnresult;
    }
    @Override
    public LockDO getRowLockByName(Stringname) {
       return (LockDO)super.getSqlMapClientTemplate().queryForObject("LockDO.selectForUpdateByLockName",name);
}

 

 

 

 

 

LockDao对应sqlMap文件里的执行sql如下:

 

<selectid="selectForUpdateByLockName"resultMap="jobLockMap" parameterClass="java.lang.String" >
        select
               ID, NAME, REMARK, IS_ENABLED
          from VENUS_LOCK
         where NAME = #value# and  IS_ENABLED = 'y'
        FOR UPDATE
  </select>
       <!-- 通过指定的代码取得操作数据锁-->
   <selectid="getLockDbByCode"resultClass="java.lang.Long"parameterClass="string">
        <![CDATA[
            select get_lock(#value#, 0) as tolock;
        ]]>
   </select>
   <!-- 通过指定的代码释放操作数据锁-->
   <selectid="releaseLockDbByCode"resultClass="java.lang.Long"parameterClass="string">
        <![CDATA[
            select release_lock(#value#) as torelease;
        ]]>
    </select>

 

 

 

通过以上代码可以很清楚的看出原理了。贷款申请提交时,为了防止一个人同时提交多笔,要按照以人维度进行业务锁的加锁处理。加锁逻辑就是锁名和人直接挂钩(就是锁名里有可以直接区分人的字段),通过执行sql:select get_lock(#锁名#, 0) as tolock;来获取数据库锁,如果获取成功,返回1。这里还去获取了一下行锁,获取的行锁它锁住的是venus_lock表的符合where条件的那些行,执行sql: select ID, NAME, REMARK,IS_ENABLED from VENUS_LOCK where NAME = #锁名#and  IS_ENABLED = ‘y’ FOR UPDATE;这里行锁是否获取成功其实都没有关系。获取到锁之后就可以执行业务逻辑了,执行完一定要释放锁,执行sql:select release_lock(#锁名#) as torelease;为了保证释放锁操作一定执行,一般在finally子句中执行它即可。通过以上的步骤,当一个人同时申请多笔时,锁名是一样的,所以获取到锁后返回值就是1、2、3…具体看你是第几个获取的了,只有第一个获取的返回值是1,从lockDao .getDbLock里的booleanresult = (lock !=null&&lock.longValue()== 1) ? true:false;就可以看出,只有第一个可以执行业务逻辑,其他就认为是没有获取到锁而抛出异常终止执行:if (LockResultEnum.没有获取到锁.equals(lockResultEnum)){
thrownewBizException(ErrorCode.LOCK_FAIL); }

 

还有一个例子:

下面的是任务分发器,它实现了Runnable接口,在任务分发器执行时会去获取各种异步任务类型的待执行任务列表,这里也用到了业务锁,调用的和上面的一样都是lockManager.getLock(…)方法。

 

public class JobDispatcher implements Runnable {
    private static final Logger LOG                 = LoggerFactory.getLogger("applyCenterJobLog");
    /** 守护线程名称 */
    private String              name;
    /** 一天秒数 */
    private static final long   ONE_DAY_SEC         = 24 * 60 * 60;
    /** 线程池队列长度 */
    private int                 queueSize           = 5;
    /** 初始处理线程数 */
    private int                 coreSize            = 5;
    /** 最大处理线程数 */
    private int                 maxSize             = 5;
    /** 空闲线程最大闲置时间 */
    private long                keepAliveTime       = ONE_DAY_SEC;
    /** 线程池接收新任务阀值 */
    private int                 hungrySize          = 2;
    /** 分发器运行状态标记 */
    private boolean             isRunning           = true;
    /** 无命令处理时休息时常(毫秒) */
    private long                noCmdSleepMillis    = 1000;
    /** 出现系统异常时休息时常(毫秒),防止把系统拖垮 */
    private long                errorCmdSleepMillis = 10000;


    private JobManager          jobManager;
    /** handler产生工厂类 */
    private JobHandlerFactory   jobHandlerFactory;


    private List<String>        jobTypeList;


    /**
     * spring init
     */
    public void init() {
        LOG.info("分发器【" + name + "】init!!!!!");
        jobTypeList = jobHandlerFactory.getJobTypeList();
    }


    /**
     * spring destroy
     */
    public void destroy() {
        LOG.warn("收到分发器【" + name + "】停止通知!!!!!");
        isRunning = false;
    }


    @Override
    public void run() {
        LOG.info("分发器【" + name + "】启动ing...");
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(queueSize);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(coreSize, maxSize, keepAliveTime, TimeUnit.SECONDS, queue);
        while (isRunning) {
            try {
                int i = 0;
                if (queue.size() < hungrySize) {
                    for (String jobType : jobTypeList) {
                        List<JobDO> jobDOList = jobManager.assignJob(jobType, queueSize - queue.size());
                        for (JobDO jobDO : jobDOList) {
                            i++;
                            JobHandler<JobDO> tmpJobHandler = jobHandlerFactory.getHandler(jobDO);
                            ExecuteJobThread<JobDO> executeCmdThread = new ExecuteJobThread<JobDO>(jobDO, tmpJobHandler);
                            executor.execute(executeCmdThread);
                        }
                    }
                } else {
                    ThreadUtil.sleep(noCmdSleepMillis, LOG);
                }
                if (i == 0) {
                    ThreadUtil.sleep(noCmdSleepMillis, LOG);
                } else {
                    i = 0;
                }
            } catch (Exception e) {
                LOG.error("dispacher 调度异常" + e.getMessage(), e);
                ThreadUtil.sleep(errorCmdSleepMillis, LOG);
            }
        }
        executor.shutdown();
    }


    /**
     * 执行分发
     */
    public void dispatcher() {
        Thread thread = new Thread(this);
        isRunning = true;
        thread.start();
    }
...//一些set方法
}

 

 

 

 

 

jobManager的assignJob方法如下:

 

public List<JobDO> assignJob(String jobType, int jobNum) {
        if (StringUtil.isBlank(jobType) || jobNum <= 0) {
            LOG.error("assignJob()参数非法jobType:{},jobNum:{}", jobType, jobNum);
            throw new BizException(ErrorCode.ILLEGAL_ARGUMENT, "参数非法!");
        }
        LockResultEnum lockResultEnum = null;
        try {
            /** 1、获取业务锁 */
    //这里调用的lockManager.getLock(...)就是之前例子里的
            lockResultEnum = lockManager.getLock(jobType, LockTypeEnum.JOB_LOCK.getCode());
            if (!LockResultEnum.获取锁成功.equals(lockResultEnum)) {//返回emptylist,dispatcher会sleep一定时间,可配置
                return new ArrayList<JobDO>(0);
            }


            return doAssignJob(jobType, jobNum);
        } catch (Exception e) {
            LOG.warn("获取锁失败", e);
        } finally {
            lockManager.releaseLock(jobType, LockTypeEnum.JOB_LOCK.getCode(), lockResultEnum);
        }
        return new ArrayList<JobDO>(0);
    }

 

 

 

 

 

从上可见,这次是要获取数据库锁和行锁都成功才行: if (!LockResultEnum.获取锁成功.equals(lockResultEnum)) {return new ArrayList<JobDO>(0);}

所以需要在venus_lock表中有对应任务类型的数据,才能使sql:select ID, NAME, REMARK,IS_ENABLED from VENUS_LOCK where NAME = #锁名#and  IS_ENABLED = ‘y’ FOR UPDATE;执行成功,获取到行锁。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/191080.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • MySQL数据库中varchar与char类型的区别

    MySQL数据库中varchar与char类型的区别

  • windows 命令行杀死进程_杀死进程命令

    windows 命令行杀死进程_杀死进程命令使用命令杀进程的几种方式:1,根据进程名称杀进程:taskkill/f/t/imqq.exe//此例是杀QQ进程2,通过进程号杀进程:taskkill/pid9396-f//规则taskkill/pid[进程号]-t(结束该进程)-f(强制结束该进程以及所有子进程)这种方式是成功的;进程号获取方式:快捷键ctrl+alt+delete找到任务管理器,PID即为进程号如果没有PID,右键名称,勾选PID即可;杀进程命令:tas…

  • 什么是窗口句柄

    什么是窗口句柄什么是窗口句柄举个例子:你有你自己的身份证号,一报身份证号,你应该知道是你了你也有名字,当然名字复杂点,并且不是唯一,没有数字来得方便,所以,窗口句柄就相当于身份证号,每个窗口都有一个编号

  • VBA数组的排序_vba函数返回值 数组

    VBA数组的排序_vba函数返回值 数组我们平时用的表格排序,只相对来说是在在表格中的升序降序。今天就好奇如果系统中实现排序他是怎么实现的呢。经过一番折腾查找,真是一看吓一跳,真是感觉蚂蚁看大象,发现排序分为:今天仅整理了最简单的两种排序。。。先来看下定义和实现的方法吧。选择排序(Selectionsort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据

  • IdeaVim中文输入体验优化

    IdeaVim中文输入体验优化IntelljIDEA是一个非常不错的JavaIDE,IdeaVim插件更是让喜欢用vim的我兴奋不已。但是IdeaVim对中文输入的支持不太好,要频繁切换中英文很麻烦。今年推荐一款插件可能解决这个问题哦。目前只支持Windows和mac。效果如下:安装插件输入法的小插件IdeaVimExtension你都用上IdeaVim了,说明我就不用介绍IDEA如何安装插件了,有问题请留言。配置在~/.ideavimrc中增加如下两行。:setkeep-english-in-normal:s

  • 腾讯云服务器配置ssl,腾讯云服务器SSL证书申请及配置[通俗易懂]

    腾讯云服务器配置ssl,腾讯云服务器SSL证书申请及配置[通俗易懂]最近在研究微信小程序,服务端需要部署在一台服务器上,查看了一下,腾讯云在搞活动,就申请了腾讯云的服务器,但是微信小程序访问需要用https协议才能请求,于是研究了一下如何申请及配置ssl证书。本人穷逼一枚,一向以节俭,所以申请了一个免费证书。申请步骤如下:1、登录证书申请页面https://console.qcloud.com/ssl/apply2、输入必要信息,通用名称及申请邮箱,点击下一步这一…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号