黑马点评项目难点-动态代理,sychronized,@Transactional失效的情况

文章目录

  • 难点1:synchronize
    • synchronized 的底层实现
    • 锁的具体操作
    • 举例说明
      • 结论
  • 难点2:动态代理和@Transactional失效问题
      • `@Transactional` 工作原理
      • 关键点
      • 示例分析
      • 正确的使用方式
      • 结论
      • 建议

难点所在代码块

    @Overridepublic Result seckillVoucher(Long voucherId) {
//        1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//        2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return fail("秒杀尚未开始");}
//        3.判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return fail("秒杀已经结束");}
//        4.判断库存是否充足if (voucher.getStock()<1) {return fail("库存不足");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()){//避免线程安全问题,确保每次5返回的都是"5",而不是不一样的5.toString()//如果直接return createVoucherOrder(voucherId);拿到的是当前VoucherOrderServiceImpl对象,而不是他的代理对象//注意seckillVoucher方法没有加@Transactional,会导致如果创建订单失败,则不会回滚,所以需要加@Transactional//事务要想生效,是Spring对VoucherOrderServiceImpl做了动态代理,拿到了他的代理对象,用createVoucherOrder(voucherId);做的事务处理//直接return createVoucherOrder(voucherId);等于直接return this.createVoucherOrder(voucherId);指的是非代理对象//也就是目标对象,也就是没有事务功能// 所以我们要拿到事务代理的对象才行IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();//拿到当前对象的代理对象return proxy.createVoucherOrder(voucherId);//这里我们直接用代理对象proxy.createVoucherOrder(voucherId);,而不是直接this.createVoucherOrder(voucherId);
//          这样proxy就是由Spring创建的,proxy就是由Spring管理的,就可以带上事务功能
//            这么写代码还要在Spring启动类加上@EnableAspectJAutoProxy(proxyTargetClass = true)}
//        7.返回订单id}

这里是一个项目难点,在于为什么要userId.toString().inter(),直接userId.toString()不行么?原因在于如果是不同的线程来了,都是一个userId,那么每次toString()都是全新的String对象,JVM不是使用了equals方法去判断是不是同一个共享资源,而是采用了对象头信息去判断(来自于ChatGPT-4o的回答)。
synchronized 关键字在 Java 中用于实现同步块或方法,以确保多线程环境下对共享资源的访问是线程安全的。它通过在进入同步块或方法时获取对象锁(也称为监视器锁)来实现这一点。

难点1:synchronize

synchronized 的底层实现

synchronized 的底层实现依赖于 JVM 的内部机制。具体来说,它使用对象头(Object Header)中的锁信息来判断锁的对象是不是同一个对象。下面是一些关键点:

  1. 对象头(Object Header)

    • 每个 Java 对象在内存中都有一个对象头。对象头中包含了对象的元数据,包括锁的信息。
    • 对象头的结构因 JVM 实现而异,但通常包含一个 Mark Word,用于存储锁标志位、哈希码、GC 信息等。
  2. 锁的状态

    • Java 中的锁有多种状态,如无锁(Unlocked)、偏向锁(Biased Lock)、轻量级锁(Lightweight Lock)、重量级锁(Heavyweight Lock)。
    • 锁的状态转换和锁的获取释放都是通过对象头中的 Mark Word 来管理的。
  3. 锁的判断

    • 当一个线程进入同步块或方法时,JVM 会尝试获取对象头中的锁。如果对象已经被其他线程锁定,当前线程会被阻塞,直到锁被释放。
    • JVM 通过比较对象头中的 Mark Word 来判断锁的对象是不是同一个对象。如果两个同步块的锁对象是同一个对象,那么它们的对象头的 Mark Word 应该相同。

锁的具体操作

当线程执行 synchronized (obj) 时:

  1. 锁的获取

    • 检查对象头中的 Mark Word 以确定当前锁的状态。
    • 如果对象是无锁状态,JVM 会尝试使用 CAS(Compare And Swap)操作将对象头的 Mark Word 更新为当前线程的锁信息,表示当前线程获得了该对象的锁。
    • 如果对象已经被锁定,JVM 会根据锁的状态(如偏向锁、轻量级锁、重量级锁)执行相应的锁获取逻辑,可能会导致线程阻塞或自旋。
  2. 锁的释放

    • 当线程退出同步块或方法时,JVM 会更新对象头中的 Mark Word 以释放锁。
    • 如果有其他线程在等待该锁,JVM 会通知等待线程重新尝试获取锁。

举例说明

synchronized (userId.toString().intern()) {// critical section
}

在这段代码中:

  1. userId.toString().intern() 返回一个字符串对象的引用。
  2. synchronized 会尝试获取该字符串对象的锁。
  3. 对象头 中的 Mark Word 会记录当前线程对该对象的锁定信息。
  4. 当另一个线程执行相同的 synchronized 块时,JVM 会检查该字符串对象的 Mark Word,发现它已被锁定,从而进行相应的处理(如阻塞或自旋)。

结论

synchronized 通过对象头中的 Mark Word 来判断锁的对象是否相同,并通过锁状态的转换和管理来实现线程同步。这确保了多个线程在访问同一个共享资源时,不会出现线程安全问题。因此如果是"5".toString()的话,这里举的例子是userId=5,那么每次toString出来的String对象都是不一样的,我们的Mark Word也是不一样的,因此无法保证是同一个共享资源,因此需要添加intern()方法,保证直接引用的是常量池里面的内容!

难点2:动态代理和@Transactional失效问题

抽取上面代码块关键两行代码进行解读:

IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();//拿到当前对象的代理对象
return proxy.createVoucherOrder(voucherId);//这里我们直接用代理对象proxy.createVoucherOrder(voucherId);,而不是直接

VoucherOrderServiceImpl的createVoucherOrder方法是@Transactional的,但是VoucherOrderServiceImpl的seckillVoucher方法是没有@Transactional的,如果没有@Transactional的方法调用了@Transactional的方法,会导致@Transactional方法失效,如果发生意外是无法回滚的,
如果直接return createVoucherOrder(voucherId);拿到的是当前VoucherOrderServiceImpl对象,而不是他的代理对象
注意seckillVoucher方法没有加@Transactional,会导致如果创建订单失败,则不会回滚,所以需要加@Transactional
事务要想生效,是Spring对VoucherOrderServiceImpl做了动态代理,拿到了他的代理对象,用createVoucherOrder(voucherId);做的事务处理
直接return createVoucherOrder(voucherId);等于直接return this.createVoucherOrder(voucherId);指的是非代理对象
也就是目标对象,也就是没有事务功能
所以我们要拿到事务代理的对象才行
在 Spring 框架中,@Transactional 注解用于声明式事务管理。了解 @Transactional 的工作原理和调用关系对于确保事务管理有效至关重要。

@Transactional 工作原理

Spring 的事务管理是通过 AOP(面向方面编程)实现的。@Transactional 注解的实现依赖于 Spring 的事务代理机制。代理对象会在方法调用前后进行事务的开启、提交或回滚操作。

关键点

  1. 代理对象@Transactional 依赖于 Spring 创建的代理对象来管理事务。当一个事务方法被调用时,实际上调用的是代理对象的方法,代理对象负责处理事务逻辑。
  2. 方法内部调用:如果一个非事务方法调用另一个标记了 @Transactional 的方法(在同一个类中),由于是直接调用,没有经过代理对象,事务不会生效。这是因为代理对象只能拦截外部调用,而无法拦截类内部的自调用。

示例分析

假设有以下类:

@Service
public class MyService {@Transactionalpublic void transactionalMethod() {// transactional code}public void nonTransactionalMethod() {transactionalMethod();}
}

在这个例子中:

  • 如果外部类(或外部对象)调用 nonTransactionalMethod(),事务管理不会生效,因为 nonTransactionalMethod() 直接调用了 transactionalMethod(),绕过了代理对象。
  • 如果外部类(或外部对象)直接调用 transactionalMethod(),事务管理会生效,因为调用通过了代理对象。

正确的使用方式

为了确保事务管理有效,可以采取以下策略:

  1. 外部调用
    确保事务方法通过代理对象调用。可以将事务方法放在另一个类中,从外部调用它们。

  2. 自调用解决方案
    使用 @Autowired 注入当前类的代理对象,确保内部调用也通过代理对象完成。

    @Service
    public class MyService {@Autowiredprivate MyService self;@Transactionalpublic void transactionalMethod() {// transactional code}public void nonTransactionalMethod() {self.transactionalMethod(); // 使用代理对象调用事务方法}
    }
    

结论

  1. 非事务方法调用事务方法

    • 事务管理不会生效,因为调用没有经过代理对象。
    • 这种情况下 @Transactional 注解会失效。
  2. 事务方法调用事务方法

    • 事务管理会生效,但前提是调用通过代理对象进行。
    • 如果事务方法内部调用另一个事务方法,必须确保调用通过代理对象,否则事务管理仍然可能失效。

建议

为了确保 @Transactional 注解有效,尽量通过外部类或注入的代理对象来调用事务方法,避免在同一个类内部直接调用带有 @Transactional 注解的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/40897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI胡言乱语

复合矢量场在多维时空折叠过程中生成了高维拓扑映射&#xff0c;使得纳米级别的存储单元能够在低能耗状态下实现高效数据交换。基于相位调制的光子流动控制确保了全息影像的即时重构&#xff0c;同时动态适应不同频段的干扰信号&#xff0c;达到最佳信噪比。 异相态转化算法在…

基于Istio的多网关运行时:配置、部署和应用

1. 引言 Istio是一个开源的服务网格&#xff0c;主要应用于简化微服务架构中的服务间通信、提供强大的监控能力以及加强服务的安全管理。通过利用Sidecar模式部署的Envoy代理&#xff0c;Istio能够在几乎无需修改服务代码的情况下&#xff0c;实现服务发现、负载均衡、加密通信…

【LinuxC语言】管理者线程函数

文章目录 前言工作者工作流程函数实现实现原理函数代码概况总结前言 在并发编程中,管理者线程函数是一个重要的组成部分,它负责管理和调度工作线程。在Linux C语言环境下,我们可以使用POSIX线程库(pthread)来创建和控制管理者线程。管理者线程通常负责添加任务到任务队列…

WRF学习——使用CMIP6数据驱动WRF/基于ncl与vdo的CMIP6数据处理

动力降尺度 国际耦合模式比较计划&#xff08;CMIP&#xff09;为研究不同情景下的气候变化提供了大量的模拟数据&#xff0c;而在实际研究中&#xff0c;全球气候模式输出的数据空间分辨率往往较低&#xff08;>100Km&#xff0c;缺乏区域气候特征&#xff0c;为了更好地研…

有哪些在本地运行大模型的方法

前言 在本文中&#xff0c;我们将看到在本地运行任何 LLM 的不同方法 1/ LMStudio LM Studio 是一款桌面应用程序&#xff0c;用于在计算机上运行本地 LLM。链接&#xff1a;https://lmstudio.ai/ 2/ Ollama Ollama 是一款工具&#xff0c;可让您在机器上本地运行开源大型语…

vue项目静态图片下载

正常情况下只需要传入图片路径就可以进行下载 methods: {downs(path, name) {//必须同源才能下载var alink document.createElement("a");alink.href path;alink.download name; //图片名alink.click();},}, 但是当我们downs方法中直接传入"/assets/load/xx…

二、分布式软总线是如何高效的传输数据和任务的

分布式软总线在HarmonyOS中高效传输数据和任务主要依靠以下几个关键技术点和设计原则: 设备快速发现与连接: 利用多种通信技术(如Wi-Fi、蓝牙、有线连接等),结合广播、多播及服务发现协议,实现设备间的快速发现与稳定连接。这包括设备的唯一标识管理、网络条件自适应选择…

【pytorch14】感知机

单层感知机模型 对于单层的感知机&#xff0c;它的激活函数是一个sigmoid 对于符号的定义做一个规范化&#xff0c;输入层每一层进行一个编号 输入是第0层&#xff0c;上标0表示属于输入层&#xff0c;下标0到n表示一共有n个节点(这里严格来说应该是0~n-1&#xff0c;为了书写…

一站式广告监测新体验,Xinstall助你广告投放更精准

在这个移动互联网飞速发展的时代&#xff0c;App推广与运营成为了每个开发者与广告主关注的焦点。然而&#xff0c;面对琳琅满目的广告平台和复杂的投放环境&#xff0c;如何精准评估广告效果、优化投放策略&#xff0c;成为了摆在面前的一道难题。今天&#xff0c;我们就来聊聊…

Jemeter--关联接口压测

Jemeter–独立不变参接口压测 Jemeter–独立变参接口压测 Jemeter–关联接口压测 案例分析 比如&#xff1a;有个波次复核接口很慢&#xff0c;优化后需要压测。但是波次复核接口数据是由另外两个接口&#xff08;配单详情、内盒信息&#xff09;的数据组合而来&#xff0c;而…

排序题目:三个数的最大乘积

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;三个数的最大乘积 出处&#xff1a;628. 三个数的最大乘积 难度 3 级 题目描述 要求 给定一个整数数组 nums …

fastadmin最新版导出数据时 表格中会有 html标签的解决办法

fastadmin 自带的导出方法&#xff0c; 是一个纯前端的导出&#xff0c; 没有请求后台的接口 当我们使用导出功能时&#xff0c; 有些数据&#xff0c; 我们在设计的时候&#xff0c;配置的是 枚举类型的 但是当我们导出数据的时候&#xff0c; 居然导出的数据中带有 html 的…

使用el-col和el-row布局,有版心,一页有两栏布局 三栏布局 四栏布局 使用vue动态渲染元素

使用Vue结合Element UI的el-row和el-col组件来实现版心布局&#xff0c;并动态渲染不同栏数的布局&#xff0c;可以通过以下步骤实现&#xff1a; 定义版心容器&#xff1a;使用el-container来定义整个页面的容器&#xff0c;其中el-header、el-main、el-footer分别定义头部、主…

k8s-第十节-Ingress

Ingress 介绍 Ingress 为外部访问集群提供了一个 统一 入口&#xff0c;避免了对外暴露集群端口&#xff1b;功能类似 Nginx&#xff0c;可以根据域名、路径把请求转发到不同的 Service。可以配置 https 跟 LoadBalancer 有什么区别&#xff1f; LoadBalancer 需要对外暴露…

Promise解决异步编程问题

一个典型的异步编程问题&#xff1a;即您尝试在循环中发起多个异步请求&#xff0c;并希望在所有请求都完成后执行某些操作。然而&#xff0c;由于JavaScript的异步性质&#xff0c;num和total的比较在循环结束时立即执行&#xff0c;而不是在所有请求都完成后执行。这可能导致…

【12321骚扰电话举报受理中心-短信验证安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

开发常识:命令行终端、库源码、开发环境阶段

目录 命令行终端 集成开发环境&#xff08;IDE &#xff09;&#xff1a;有插件校验等限制&#xff0c;成功率低于操作系统 库源码 github上搜 官网 UNPKG托管开源的包 专业名词 环境 开发&#xff1a;本地机 开发和调试 生产&#xff1a;最终部署 测试&#xff1a;…

交流负载箱的主要功能有哪些?

交流负载箱可以模拟各种实际用电设备的功率、电流、电压等参数&#xff0c;使得电源系统在运行过程中能够承受实际负载的考验&#xff0c;确保电源系统的稳定运行。通过交流负载箱对电源设备进行测试&#xff0c;可以检测出电源设备在过载、短路等异常情况下的保护功能是否正常…

Linux和mysql中的基础知识

cpu读取的指令大部分在内存中&#xff08;不考虑缓存&#xff09; 任何程序在运行之前都的加入到内存。 eip->pc指针&#xff0c;指明当前指令在什么位置。 代码大概率是从上往下执行的&#xff0c;基于这样的基本理论。既可以将一部分指令加载到CPU对应的缓存中&#xf…

解决zip文件中文乱码问题

后台微服务运行在linux环境里&#xff0c;前端Vue。在一个项目中&#xff0c;把后台的文件打包成zip&#xff0c;下载到前台。结果发现zip文件名本身乱码&#xff0c;zip文件内压缩的文件也是乱码。所谓乱码&#xff0c;程序员都见过&#xff0c;就是中文变成了乱七八糟的字符。…