第十五章 事务边界问题

 事务边界问题
我们先回顾一下我们现在写的代码:

[img]http://dl.iteye.com/upload/attachment/243941/7b1053d2-2153-359c-b823-fe6a4620db0e.bmp[/img]

可以看到我们现在写的代码是Dao层中每一个方法都有一个事务包围着.

我们现在以银行同行转帐为例
因为Dao层只涉及到与数据库的交互,所以转帐这个方法应该是在服务层,那么这个时候我们在服务层有一个转帐的方法供我们调用.
可以看到程序结构的情况如下:
Dao层:

[img]http://dl.iteye.com/upload/attachment/243943/87cc3332-d1d6-36b8-a4c0-bcfc26b3016a.bmp[/img]

Service层:

[img]http://dl.iteye.com/upload/attachment/243933/9c89ed1c-7f06-3c2d-91e1-c17d962ab31e.bmp[/img]

我们可以看到现在在服务层中的转帐方法需要调用Dao层的两个方法,但是请注意,Dao层的两个方法,每个方法都有事务包围.如果帐户减减调用成功, 帐户++的方法调用出错,回滚了,请问帐户减减这个方法会回滚吗?

肯定不会,那么这个时候就会出现钱不见了的问题了?

这个时候的解决方案是将事务的开始与提交移到服务层,那这个时候就可以解决这个问题

[img]http://dl.iteye.com/upload/attachment/243935/e780b6e8-1834-37a0-bfd8-6a4c5578ed7f.bmp[/img]

这种解决方法叫OpenSessionInView模式,因为我们现在还没有学到Spring,不能给服务层的方法进行拦截过滤,但是我们可以给Sevlet进行过滤,利用过滤器的功能.

看一下结构图

[img]http://dl.iteye.com/upload/attachment/243945/a4ff7e2e-bc69-3ea8-8e2a-ed4f1ac23287.bmp[/img]


使用getCurrentSession()来获取Session在Hibernate.cfg.xml中必须配置一个开关
<!-- 确定以何种方式产生session-->
<property name="current_session_context_class">thread</property>
此开关的配置值有两个: thread, jta
那么jta什么时候用呢?现在只理解即可,不需要掌握.

我们以跨行转帐为例子

[img]http://dl.iteye.com/upload/attachment/243937/ba2f6ca0-2d9a-3bd2-bc9a-efc0831cd8de.bmp[/img]

我们知道招行和工行不可能是同一个数据库,这个时候跨行转帐方法中的事务就得维护两个数据库之间的数据同步,这个时候就涉及到分布式的处理,也叫分布式的事务,那么这个时候就得设置为jta了, jta叫分布式事务管理, Tomcat不支持分布式事务的功能, 只有大型的收费的WEB应用服务器才支持分布式的事务管理,如Weblogic,WebSharp…在这里我们只做一下简单的了解即可.

再回头看上面的问题,当我们将session的事务开始与事务结束放大到了Action的范围,并且getCurrentSession()又能够保证我们在一个线程内操作的是一个session,那这个时候,在WEB环境下,我们就可以解决我们懒加载(lazy)的问题了.
下面请看示例:
<think> 我们正在讨论数据边界对齐的问题。根据引用内容,数据边界对齐是指数据在内存中存放时,其起始地址是某个边界(如字长)的整数倍。这样的存储方式可以提高CPU访问内存的效率,因为许多硬件平台在访问未对齐的数据时可能会需要额外的周期或引发异常。 不按边界对齐则是指数据存放的起始地址不是某个特定边界(如字长)的整数倍。这可能导致访问效率降低,因为CPU可能需要多次访问内存才能读取完整的数据,甚至在某些架构中会导致错误。 原理: 1. 硬件限制:许多处理器在访问内存时,如果数据跨越了其自然边界(比如4字节的int类型数据跨越两个4字节边界),则需要两次访问才能读取完整数据。例如,在一个32位系统中,一次内存访问通常可以读取4个字节(一个字),如果这4个字节恰好在一个字内,则一次读取即可;但如果一个int类型数据存储在两个字的边界上(比如前两个字节在第一个字,后两个字节在第二个字),则需要两次读取,然后将两部分组合起来,这样效率较低。 2. 性能优化:对齐的数据访问可以利用处理器的缓存机制,提高缓存命中率,从而提升访问速度。 3. 规则:编译器通常会对结构体成员进行自动对齐,以保证每个成员都满足对齐要求。对齐规则通常由两个因素决定:一是数据成员本身的大小(或预编译指令指定的对齐系数),二是结构体整体对齐要求。 根据引用[1]和[3]: - 结构体成员对齐:每个数据成员的起始地址要满足:min(指定对齐系数, 该成员类型大小)的整数倍。 - 结构体整体对齐:结构体总大小必须是min(指定对齐系数, 结构体中最大成员大小)的整数倍。 例如(引用[3]中的例子): 假设有一个结构体,其成员包括double(8字节)、char(1字节)、int(4字节)、char(1字节)。在64位系统中,默认对齐系数可能是8。那么: - double a: 放在偏移0处,占8字节(0-7) - char b: 放在偏移8处,占1字节(8) - 接下来是int c: 它需要4字节对齐,而min(8,4)=4,所以它应该放在4的倍数位置。当前偏移是9,不是4的倍数,所以填充3个字节(9,10,11),然后从偏移12开始存放int c(12-15) - char d: 放在16处,占1字节(16) - 然后结构体整体对齐:最大成员是double(8字节),所以整体大小必须是8的倍数。当前大小是17,需要填充到24(因为24是8的倍数)?实际上,在64位系统中,这个结构体的大小为24(引用[2]中显示64位下为24字节)。具体计算: 0: double a (8字节) 8: char b (1字节) -> 然后为了对齐int,需要填充3字节(9,10,11),然后12-15是int c(4字节) 16: char d (1字节) -> 然后为了整体对齐(最大成员8字节,所以整体大小要8的倍数),需要填充7个字节(17-23)到24。 而不按边界对齐则没有这些填充,因此结构体的大小会小一些(如上面的例子,如果不填充,大小是17字节),但访问效率可能降低。 区别总结: 1. 效率:对齐的数据访问速度快,因为CPU通常可以一次读取;不对齐可能需要多次读取,甚至在某些架构(如ARM)上导致硬件异常。 2. 空间:对齐会通过填充字节增加空间开销;不对齐则空间紧凑。 3. 可移植性:不对齐的数据结构在不同的硬件平台上可能出现问题,而对齐则能保证可移植性。 另外,引用[4]用仓库房间和箱子的比喻解释了为什么不对齐会导致访问效率降低:如果数据横跨两个存储单元(如两个“字”),就需要两次访问。 因此,在编写程序时,特别是涉及结构体定义时,我们通常让编译器自动对齐,以优化性能。在特殊情况下(如需要节省内存)可以通过编译指令调整对齐方式(如#pragma pack(1)),但需要注意可能带来的性能损失和可移植性问题。 相关问题: 1. 如何通过编译指令调整结构体的对齐方式? 2. 在什么情况下应该考虑不对齐存储? 3. 数据对齐对多线程程序的内存访问有什么影响? 4. 不同硬件平台(如x86和ARM)对数据未对齐的访问处理有什么不同? </think> ### 数据边界对齐与不对齐的区别和原理 #### 一、核心区别 1. **对齐存储** - 数据起始地址是类型大小的整数倍(如4字节int类型地址为4的倍数) - 编译器自动填充空字节保证对齐 - **优点**:CPU单次访存即可读取数据(效率高)[^4] - **缺点**:存在内存空间浪费(填充字节) 2. **不对齐存储** - 数据起始地址无特定限制 - 无填充字节,内存更紧凑 - **缺点**: - CPU需多次访存才能读取完整数据(效率低) - 部分架构(如ARM)直接引发硬件异常[^1] - 增加总线事务和功耗 #### 二、底层原理 1. **硬件访存机制** - CPU按"字"(如4字节/8字节)访问内存 - 对齐数据完整位于单个存储单元内(如仓库的同一房间) - 不对齐数据横跨多个存储单元(需多次开门访问)[^4] *示例:32位系统读取未对齐的int(0xC00D):* ```c // 地址0xC00C: [字节0][字节1][字节2][字节3] // 第一次读取 // 地址0xC00D: [字节4][字节5][字节6][字节7] // 第二次读取 // 需拼接字节3+字节4才能得到完整int → 性能损失 ``` 2. **编译器对齐规则**(以结构体为例)[^1][^3] - **成员对齐**:`offset = min(类型大小, #pragma pack值)`的倍数 - **整体对齐**:结构体大小 = `min(最大成员大小, pack值)`的倍数 *示例:64位系统下结构体* ```c #pragma pack(8) struct { // 内存布局(数字为填充字节) double a; // [a0-a7] char b; // [b][x][x][x][x][x][x][x] // 填充7字节使int对齐 int c; // [c0-c3] }; // 总大小=24字节(8的倍数) ``` #### 三、关键影响对比 | **特性** | 对齐存储 | 不对齐存储 | |------------------|--------------------------|--------------------------| | 内存利用率 | 较低(填充空间) | 较高(无填充) | | CPU访问速度 | ⭐⭐⭐⭐ 单周期完成 | ⭐⭐ 多周期拼接 | | 硬件兼容性 | 全架构支持 | ARM等架构可能崩溃[^1] | | 典型应用场景 | 高性能计算、系统内核 | 嵌入式设备(需显式指定) | | 编译器控制 | `#pragma pack(n)`[^1] | `#pragma pack(1)` | #### 四、现实案例 1. **64位系统的实际位宽** 虽然称为"64位机",但内存控制器可能仍按32位边界访问(引用[2]验证) ```c // 64位系统测试代码[^2] printf("%d", sizeof(struct { double a; char b; })); // 输出16而非9 → 自动填充7字节对齐 ``` 2. **网络传输优化** 网络数据包常用`#pragma pack(1)`取消对齐,牺牲性能换取空间节省[^5]。 --- ### 相关问题 1. 如何在C语言中强制取消结构体的对齐填充? 2. 为什么ARM架构对未对齐访问更敏感? 3. 数据对齐如何影响CPU缓存命中率? 4. 在内存受限的嵌入式系统中如何权衡对齐策略? 5. 不同编程语言(如Go/Rust)如何处理数据对齐?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值