java内存模型(JMM)
java内存模型 (java memory model JMM)指定了java虚拟机如何与计算机的主存RAM进行工作(可以类比RAM和cpu L1 L2 L3高速缓存)
java内存模型定义了线程和主内存之间的抽象关系
线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存。
工作内存和java内存模型一样也是一个抽象的概念,它其实并不存在,其涵盖了缓存、寄存器、编译器优化以及硬件等。
jvm采用java内存模型机制来屏蔽各个平台和操作系统之间内存访问的差异,以实现让java程序在各种平台下达到一致的内存访问效果。比如C语言中的int变量,在某些平台下占用两个字节的内存,在某些平台下占用四个字节的内存,java则在任何平台下,int类型就是四个字节,这就是所谓的一致内存访问效果。
1. 自增操作
++这个操作,分成三步:
- 线程从主内存中读出来最新的value值
- 会放到线程的虚拟机栈的栈帧中的操作栈上,压进去,然后弹出,供cpu计算加1,计算完成再压入,再弹出
- 线程将新的值再写回到内存中。
可能存在的问题:
- 在线程执行到第2步的时候,另一个线程在可能已经修改了主内存中value的值,而写写回后将另一个线程的操作覆盖掉了
- 要考虑并发和并行的情况
- 当线程1和线程2在同一个cpu上进行切换,会共享工作内存,这是并发的情况
- 当线程1和线程2在不同cpu上进行切换,这是并行的情况
2. instance = new Singleton();
instance = new Singleton();
编译为JVM代码,需要做三件事情
-
要分配内存
-
调用Singleton的构造函数,字节码指令是invokeSpecial
-
给instance赋值,指向新创建的对象
其中2和3是可能发生指令重排操作的,这也是单例模式下双重校验仍要使用volatile的原因
3. y = x;
y=x是非原子性的,因为其包含两个步骤:
-
执行线程从主内存中读取x的值(如果x已经存在于执行线程的工作内存中,则直接获取),然后将其存入当前线程的工作内存中
-
在执行线程的工作内存中修改y的值为x,然后将y的值写入主内存中
这两步都是原子操作,但合在一起就不是原子操作了
4. 64位数据操作
比如在AtomicLong中,
/**
* Records whether the underlying JVM supports lockless
* compareAndSet for longs. While the intrinsic compareAndSetLong
* method works in either case, some constructions should be
* handled at Java level to avoid locking user-visible locks.
*/
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
这个是AtomicInteger没有的
long是64位的,
与cpu通信是通过总线进行的,有数据总线、控制总线、地址总线。
如果数据总线是32位的,那么long就要分两次来传输给cpu,这样可能就不好保证原子性了
如果不支持,可能就需要对总线进行加锁了,比如对数据总线进行加锁,我要把高位和低位都拿过去,然后再把锁释放,这样可能效率要低一些。如果支持,就不需要锁总线