最近看了两篇博文:
主题:java线程安全总结
主题:线程安全总结(二)
看完之后对自己触动很大,和自己以前的理解有很大的矛盾.当时发站内信给作者的疑惑内容如下:
写道
看完之后,有2个疑问:
⒈java内存模型,没有说内存模型的具体结构,如:heap,java stack,method area等.
2.关于"那么,何谓可见性? 多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的。当线程操作某个对象时,执行顺序如下:
(1) 从主存复制变量到当前工作内存 (read and load)
(2) 执行代码,改变共享变量值 (use and assign)
(3) 用工作内存数据刷新主存相关内容 (store and write) JVM规范定义了线程对主存的操作指令:read,load,use,assign,store,write。当一个共享便变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。"
疑问:为什么要有很多的副本呢?然后再刷新到主存呢?这样做不是效率很低?
java中的方法是值传递,参数是对象的话,只是对象指针的一个副本,如:
public void fun(Object obj){
// 改变obj的属性
obj.property = "abc";
// 改变obj指针的值,对方法外面的obj无影响
obj = new Object();
obj.property = "def";
}
既然每个线程中java stack传递的都是指针(指向heap内的对象),为什么还要复制一个heap内对象的副本呢?
后来看到第二篇的时候
写道
看完之后明白了,我以前的理解只是基于内存中的模型,而你说的read,load,use,assign,store,write针对的是对CPU,寄存器,内存等统一抽象的工作内存的操作
谢谢分享!让我对线程安全的理解更深入一步
今天,特意的查询了这方面的内容,博客地址:http://kenwublog.com/illustrate-memory-reordering-in-cpu
写道
对主存的一次访问一般花费硬件的数百次时钟周期。处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序。也就是说,程序的读写操作不一定会按照它要求处理器的顺序执行。
重排序的背景
我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多。当CPU的计算速度远远超过访问cache时,会产生cache wait,过多的cache wait就会造成性能瓶颈。
针对这种情况,多数架构(包括X86)采用了一种将cache分片的解决方案,即将一块cache划分成互不关联地多个 slots (逻辑存储单元,又名 Memory Bank
或 Cache Bank),CPU可以自行选择在多个 idle bank 中进行存取。这种 SMP
的设计,显著提高了CPU的并行处理能力,也回避了cache访问瓶颈。
Memory Bank的划分
一般 Memory bank 是按cache address来划分的。比如 偶数adress 0×12345000 分到 bank 0, 奇数address 0×12345100 分到 bank1。
重排序的种类
编译期重排。编译源代码时,编译器依据对上下文的分析,对指令进行重排序,以之更适合于CPU的并行执行。
运行期重排,CPU在执行过程中,动态分析依赖部件的效能,对指令做重排序优化。
实例讲解指令重排序原理
为了方便理解,我们先来看一张CPU内部结构图。

从图中可以看到,这是一台配备双CPU的计算机,cache 按地址被分成了两块 cache banks,分别是 cache bank0
和 cache bank1
。
最后引用博客作者的一句话:
写道
先抛开java虚拟机不谈,我们都知道,现在的计算机,cpu在计算的时候,并不总是从内存读取数据,它的数据读取顺序优先级是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。当个多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特性:原子性,有序性,可见性
而实现这个的就是编译期重排和运行时重排序
分享到:
相关推荐
- **有序性**:防止编译器和处理器对指令进行重排序,以维护操作间的顺序性。 ##### 2.2 监视器(Monitor) 在Java中,每个对象都包含一个监视器(Monitor),用于提供对临界区代码的互斥访问。临界区是指不允许多...
此外,CPU的内存模型和指令重排序对Java代码的执行也有影响。Java的JIT编译器会根据CPU特性进行优化,包括调整指令执行顺序,但这也可能导致数据依赖性的不确定性。为了确保正确性,Java内存模型(JMM)定义了一组...
6. **指令重排序**:编译器或处理器为了优化性能,可能改变代码执行顺序,但结果必须保持一致。 7. **Synchronized关键字内存语义**:确保线程在访问共享资源时的互斥,保证了可见性和有序性。 8. **Volatile...
jcstress的核心设计理念是帮助开发者发现那些在正常情况下难以复现的并发问题,这些问题可能由于CPU缓存、指令重排序等原因导致。它提供了丰富的测试用例模板,允许开发者创建自定义的并发测试,以确保程序在多线程...
这是因为编译器和CPU可能会进行指令重排序,使得构造对象的操作在设置指针之前发生,从而破坏了可见性和一致性。 **内存模型与数据竞争:** C++标准中的内存模型允许编译器和硬件进行一定的优化,这些优化可能导致...
8. **操作系统**:进程与线程的区别,内存管理,虚拟内存,磁盘调度算法,以及操作系统的并发与同步原语。 9. **设计模式**:掌握常见的23种设计模式,如工厂模式、单例模式、观察者模式、装饰器模式等,并能灵活...
3. **处理器知识**:理解处理器的工作原理,如CPU架构、指令集、流水线技术,对于优化代码和理解性能瓶颈至关重要。学习汇编语言,尽管不常用,但能帮助你更好地理解计算机底层运行机制。 4. **内核分析**:操作...