开发过程中,有时候为了解决多线程竞争问题需要加锁,通常锁定的对象是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. Windows 8实例教程系列 - 数据绑定基础实例

    原文:Windows 8实例教程系列 - 数据绑定基础实例 数据绑定是WPF,Silverlight以及Windows Phone应用开发中最为常用的开发技术,在基于XAML的Windows Stor ...

  2. jconsole 连接 eclipse启动项

    eclipse 启动java项目默认情况下不开启jmx远程观看,假设需要看看内存使用情况对项目执行的线程等信息,能eclipse添加启动参数: -Dcom.sun.management.jmxremo ...

  3. 8.19! 今天我有18生日,点击阅读或顶部 尾随幸运的一天!生日知识!↓——【Badboy】

    话说得生疼.我已经想到了17那年,那是一年的时间!我在这里7.24我认为这是我的生日 结果到处宣传 ,首页疑问发现自己是错的生日 按照农历计勒把我羞辱. 不依照原农历7.24的  今天的国家今天的日历 ...

  4. Visual Studio 2015使用EF6的CodeFirstFromDB模式操作Sqlite数据库时Provider问题

    传送门 什么是CodeFristFromDB 问题:查询数据是遇到 “/”应用程序中的服务器错误. No Entity Framework provider found for the ADO.NET ...

  5. CDH秘籍(两):cloudera Manager存储监控数据

    概述 上一篇文章分析了cloudera manager中监控数据.中心数据的存储方式,如何配置外部表等.这一篇文章进一步分析监控数据的存储,配置,调优等. Service Monitor 和 Host ...

  6. Unity带有网络功能——创建服务,并连接到一个特定的服务

    游戏本身需要在网络上创建服务,然后其他的游戏能够连接到这个服务,此外真实场景现在玩同样的游戏效果一起. 该方法是创建一个服务呼叫Network.InitializeServer( ): 是Networ ...

  7. Android 墙纸设置代码 详细说明

    使游戏图像列表.思考添加壁纸功能.我发了一些资料. 1 别忘记在ApplicationManifest.xml 中加上权限的设置. <uses-permission android:name = ...

  8. java反思reflect 分析Object物

    直接看它的一个例子 </pre><pre name="code" class="java">package reflect; impor ...

  9. 【SSH之旅】一步学习的步Struts1相框(三):分析控制Struts1示例

    部分博客,,先后实施了Struts抽样.现在我们来分析这个样本.具体解释Struts1. Struts1的几个核心组件是值得我们注意的: (1)ActionServlet(核心控制器). (2)Act ...

  10. 随笔 - Internet缓存文件

    Internet缓存文件 本文地址: http://blog.csdn.net/caroline_wendy 互联网上全部的使用记录, 图片, 音乐, 视频, 都会缓存在本地. 1. 位置: IE-& ...