开发过程中,有时候为了解决多线程竞争问题需要加锁,通常锁定的对象是class,object,method,但在特定时候我们需要更细粒度的加锁,也就是根据不同输入参数来锁定不同的资源,这样只有调用此方法的不同线程传参一样才会进行竞争。

比如一个简单的例子:假设系统为用户提供借款,每月有个限额。每月的借款记录都记在transaction_detail表中,此时当一个用户需要进行借款的时候,我们需要进行一下操作:

method:borrow_money

1、判断用户是否达到借款限额,本月已借款量:select sum(amount) from transaction_detail where user_id=XX;

2、其他业务处理

3、插入transaction_detail

但是以上三步并非原子的,也就是假设用户限额为3w,已借款量1.5w(还可借1.5w),现在有两个线程a、b,每个线程都需要借款1w。

当线程a执行完第一步判断,此时线程a挂起。

线程2开始执行,由于此时借款1w<可借的1.5w,于是线程2执行下去成功贷款1w,插如新的贷款记录。

a线程此时继续执行2、3步也成功贷款了1w,这样该用户当月贷款量已达3.5w大于总限额了(违背了每月3w的限额)。导致这个情况就是以上3步非原子的,在我们执行借款、插入借款记录的贷款状态很可能与我们判断当月已贷款量已经不同了。

这样我们就需要对上面的三步进行加锁同步,但是如果我们将上诉方法borrow_money使用synchronized,将会引发很严重的效率问题,因为这样整个系统的所有线程在执行borrow_money都是竞争关系的,这样就会造成系统性能瓶颈。

于是我们可以使用一种更细粒度的加锁机制。可以对特定字符串加锁来保证操作的原子性同时减少线程对资源的竞争,这个特定字符串需要与用户一对一的关联,同时确保不会影响系统其它操作,所以可以使用:特殊字符串(确保字符串具有特殊性)  + id。

而且我们的字符串需要到jvm的常量池中获取这样确保对于相同的用户获取的字符串是一个对象。

String syncKey = ("borrow_money_" + userId).intern();
synchronized (syncKey) {
  
  //1、判断用户本月已借款量:select sum(amount) from transaction_detail where user_id=XX;   //2、其他业务处理   //3、插入transaction_detail }

如上便解决对于同一个key 独占访问,但这仅限于同一个jvm,如果服务器部署在集群上便无法达到预期效果。

此时可以将每月总借款信息记录在一张表中,这样在判断与操作的时候可以根据这条记录来判断状态

或者使用分布式的锁,如:ZooKeeper进行管理。

Java业务原子性的一种实现(key 独占访问)的更多相关文章

  1. Java分布式锁的三种实现方案(redis)

    方案一:数据库乐观锁 乐观锁通常实现基于数据版本(version)的记录机制实现的,比如有一张红包表(t_bonus),有一个字段(left_count)记录礼物的剩余个数,用户每领取一个奖品,对应的 ...

  2. java多线程中的三种特性

    java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...

  3. Java开发中的23种设计模式(转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  4. java 遍历Map的四种方式

      java 遍历Map的四种方式 CreationTime--2018年7月16日16点15分 Author:Marydon 一.迭代key&value 第一种方式:迭代entrySet 1 ...

  5. 【转载】Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍

    转载地址:http://blog.csdn.net/truong/article/details/46711045 关键字:Redis的Java客户端Jedis的八种调用方式(事务.管道.分布式…)介 ...

  6. Java创建线程的四种方式

    Java创建线程的四种方式 1.继承Thread类创建线程 定义Thread类的子类,并重写该类的run方法,run()方法的内容就是该线程执行的内容 创建Thread子类的实例,即创建了线程对象. ...

  7. Java避坑宝典《Java业务开发常见错误100例》上线了

    写这个专栏的缘起 之前我写过一篇博客:<朱晔的互联网架构实践心得S2E2:写业务代码最容易掉的10种坑>,引起的关注还是挺多的.后来和极客时间的编辑一拍即合决定以这个为题写一个专栏.其实所 ...

  8. Java多线程--原子性、可见性、有序性

    计算机的内存模型: 计算机在运行行程序的时候,指令由CPU执行,计算机上数据存放在物理内存当中,CPU在执行指令的时候免不了要和数据打交道.刚开始,还相安无事的,但是随着CPU技术的发展,CPU的执行 ...

  9. Springboot Jpa: [mysql] java.sql.SQLException: Duplicate entry 'XXX' for key 'PRIMARY'

    前言 1.问题背景 偶尔会出现登录请求出错的情况,一旦失败就会短时间内再也登录不上,更换浏览器或者刷新可能会暂时解决这个问题. 项目运行日志如下: 2022-07-21 09:43:40.946 DE ...

随机推荐

  1. hdu 4454 Stealing a Cake(三分之二)

    pid=4454" target="_blank" style="">题目链接:hdu 4454 Stealing a Cake 题目大意:给定 ...

  2. Android引入高速缓存的异步加载全分辨率

    Android引进高速缓存的异步加载全分辨率 为什么要缓存 通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图.图.动不动就是图文混排.以图代文,假设这些图片都载入到 ...

  3. 无尽的循环ViewPager

    现在的情况 不改变的源代码,什么时候ViewPager滑动到最后item的时候,他就无法再往右滑动:当ViewPager滑动到第一个item的时候,他也无法再往前滑动. (以上全是废话) 设想 我们能 ...

  4. Java线(一个):线程安全的和不安全

    当我们看JDK API什么时候,总是找一些类描述说:,线程安全或线程安全,例如StringBuilder在,么一句,"将StringBuilder 的实例用于多个线程是不安全的.假设须要这种 ...

  5. 人活系列Streetlights (秩)

    人活着系列之Streetlights Time Limit: 1000MS Memory limit: 65536K 题目描写叙述 人活着假设是为了家庭,亲情----能够说是在这个世界上最温暖人心的, ...

  6. C++ Primer笔记4_静态成员类_IO库

    1.静态成员类 static成员变量与函数 static成员变量:必须在类外初始化.(const或引用类型变量必须在构造函数初始化列表里初始化) static成员函数: 不依赖于类.相当于类里的全局函 ...

  7. CentOS 忘记 root password处理

    在倒计时启动时间,按任意键进入,例如下面的页面 输入 e. 进入这个页面.上下移动至高亮条到中间 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQ29yZV ...

  8. JavaWeb框架的基石

    JavaWeb框架的基石(一) 初学JavaWeb开发,请远离各种框架,从Servlet开始.         Web框架是开发者在使用某种语言编写Web应用服务端是关于架构的最佳实践.很多Web框架 ...

  9. Angularjs里面跨作用域

    Angularjs里面跨作用域的实战!   好久没有来写博客了,最近一直在用Google的AngularJS,后面我自己简称AngularJS就叫AJ吧! 学习AngularJS一路也是深坑颇多啊-- ...

  10. 具体分析Struts工作流程

    watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXV3ZW56aGU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA ...