AOP中多线程多切面场景的安全性问题
1. CGLIB多个切面是按照优先级执行的,@Order()来控制切面切入顺序
2. 探针死锁是发生在多个探针循环依赖的关系,多切面直接不会出现这种关系,因为切面的作用就是加强被切业务的功能,所有加强逻辑都可以放在一个切面,如果出现多切面互切那一定是设计不合理,画蛇添足的做法。
字节码执行
方法调用在JVM中转换成的是字节码执行,字节码指令执行的数据结构就是栈帧(stack frame)。也就是在虚拟机栈中的栈元素。虚拟机会为每个方法分配一个栈帧,因为虚拟机栈是LIFO(后进先出)的,所以当前线程正在活动的栈帧,也就是栈顶的栈帧,JVM规范中称之为“CurrentFrame”,这个当前栈帧对应的方法就是“CurrentMethod”。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作
运行时数据区
栈帧的数据结构主要分为四个部分:局部变量表、操作数栈、动态链接以及方法返回地址(包括正常调用和异常调用的完成结果)。下面就一一介绍下这四种数据结构。
局部变量表(local variables)
当方法被调用时,参数会传递到从0开始的连续的局部变量表的索引位置上。栈帧中局部变量表的长度存储在类或接口的二进制表示中。阅读Class文件会找到Code属性,所以能知道local variables的最大长度是在编译期间决定的。一个局部变量表的占用了32位的存储空间(一个存储单位称之为slot,槽),所以可以存储一个boolean、byte、char、short、float、int、refrence和returnAdress数据,long和double需要2个连续的局部变量表来保存,通过较小位置的索引来获取。如果被调用的是实例方法,那么第0个位置存储“this”关键字代表当前实例对象的引用。这个可以通过javap 工具查看实例方法和静态方法对比字节码指令的0位置。例子也可以参考JVM 字节码指令对于栈帧数据操作举例
操作数栈(operand stack)
操作数栈同局部变量表一样,也是编译期间就能决定了其存储空间(最大的单位长度),通过 Code属性存储在类或接口的字节流中。操作数栈也是个LIFO栈。
操作数栈是在JVM字节码执行一些指令(第二部分会介绍一些指令集)时创建的,主要是把局部变量表中的变量压入操作数栈,在操作数栈中进行字节码指令的操作,再将变量出操作数栈,结果入操作数栈。同局部变量表,除了long和double,其他类型数据都只占用一个栈的单位深度。
动态链接
每个栈帧指向运行时常量池中该栈帧所属的方法的引用,也就是字节码的发放调用的引用。动态链接就是将符号引用所表示的方法,转换成方法的直接引用。加载阶段或第一次使用时转化为直接引用的(将变量的访问转化为访问这些变量的存储结构所在的运行时内存位置)就叫做静态解析。JVM的动态链接还支持运行期转化为直接引用。也可以叫做Late Binding,晚期绑定。动态链接是java灵活OO的基础结构。可以参考一个例子来加深理解从字节码指令看重写在JVM中的实现
方法返回地址
方法正常退出,JVM执行引擎会恢复上层方法局部变量表操作数栈并把返回值压入调用者的栈帧的操作数栈,PC计数器的值就会调整到方法调用指令后面的一条指令。这样使得当前的栈帧能够和调用者连接起来,并且让调用者的栈帧的操作数栈继续往下执行。
方法的异常调用完成,主要是JVM抛出的异常,如果异常没有被捕获住,或者遇到athrow字节码指令显示抛出,那么就没有返回值给调用者。
结论
1. CGLIB字节码插装是在解释器解释运行和即时编译的过程进行的,多个切面切一个服务的方法对应都会在虚拟机栈生成一个线程私有的栈帧结构,字节码执行只执行栈顶栈帧结构--CurrentFrame,线程安全。
2. 代理对象是在运行时生成,代理类除了自己的加强逻辑之外还会调用被代理类的方法--被切方法。
3. 根据实际业务试验,多线程执行多个切面去切同一个方法时,默认按照代理类类名的ASCII码进行有序执行,如果用了@Order注解,就会按照我们自定义的优先级进行执行。
4. 整个切面执行到产生代理类再到加强代理方法运行的全过程都是有序进行不会出现死锁或争抢临界资源问题。
推荐阅读