-------------------------------------------------------------主要内容--------------------------------------------------------
1.JMM内存模型
2.主内存与工作内存之间数据同步的8大原子操作
3.并发的三个问题
· 原子性
· 可见性
· 有序性
4.volatile内存语义 & 内存屏障
---------------------------------------------------------------------------------------------------------------------------------
1.JMM内存模型
JMM(JAVA memory model)即java内存模型,是一种抽象的概念,并不实际存在。JMM定义了数据的访问方式,包括线程私有的工作内存和线程间共享的共享内存。
线程的工作内存:
(1)线程独享,不存在线程安全性问题。
(2)拷贝的是主内存的副本。若主内存是基本数据类型,直接将值拷贝到线程的栈空间;若是实例对象,那么存储的是对象的引用。
(3)处理完成之后,会将工作内存的值刷回到主内存。
共享内存:
存储线程间共享的变量,可被多个线程访问,存在线程安全性问题。
※注意:共享内存并不单单指JVM堆内存,它是一个虚拟的概念,在硬件层面可包含寄存器、缓存和主内存。不要和JVM的线程模型混淆。
2.主内存与工作内存之间数据同步的8大原子操作
(1)Lock(锁定):将主内存中待访问的共享变量加锁。
(2)read(读取):将共享变量的副本拷贝出来,以供后续的load操作。
(3)load(加载):将读取的功能共享变量副本加载到工作内存。
(4)use(使用):将工作内存的变量传递给JVM执行引擎。
(5)assign(赋值):执行引擎将处理完的变量赋值给工作内存的变量。
(6)store(存储):将线程工作内部的变量传递出来,以便后续的write操作。
(7)write(写入):将工作内存的变量副本写入到共享内存。
(8)unlock(解锁):解除主内存中共享变量的锁定。
数据同步规则:
(1)变量只能在主内中诞生
(2)不允许一个线程无原因地(没有任何assign操作)刷新主内存中的变量
(3)一个变量同一时刻只能被一个线程锁定。可多次加锁,加锁和解锁成对出现。
(4)对变量lock,会清空工作内存中这个变量的值
(5)对变量unlock之前必须把值wrtie回主内存
3.并发的三个问题
原子性
原子性指的是一个操作是原子的,一旦开始就不会被别的线程影响。
(1)在JVM中,对基本数据类型的读写是原子的。
(2)还可以用synchronized和Lock保证操作的原子性。
可见性
由于线程会拷贝主内存的变量到工作内存,线程的工作内存又是独享的,对其他线程不可见。所以线程对变量的修改,是无法及时被其他线程感知到的。
在java中,可以使用volatile关键字来修饰成员变量。被volatile关键字修饰的成员变量,写是先于读的——一个线程修改了这个变量,会立刻将值刷回主内存,其他读线程会强制将工作内存中的变量副本失效,重新读取主内存中变量的值。这就保证了变量的修改能及时被其他线程感知到。在字节码层面,被volatile修饰的变量,会多出一个ACC_VOLATILE的特殊修饰。
另外,synchronized和lock保证了同一时刻变量只能被一个线程访问,并且在释放锁之前将变量的值刷新回主内存,也能保证可见性。
有序性
为了保证cpu的高性能,jvm会将操作指令进行重排。但是指令重排在多线程可能出现一些难以预料的问题。比如经典的单例模式的懒汉式实例化:
因为new DoubleCheckLock()可以分为以下3步
(1)memory=allocate(); // 分配内存空间
(2)instance(memory); // 初始化对象
(3)instance=memory; // instance指向刚分配的内存地址
由于指令重排只保证单线程语义完整性,那么(2)和(3)之间可能发生指令重排,就变成下面这样:
(1)memory=allocate(); // 分配内存空间
(2)instance=memory; // instance指向刚分配的内存地址
(3)instance(memory); // 初始化对象
线程A如果执行完(2)之后,失去了CPU的使用权,线程B此时来使用这个未初始化完成的变量,就会出现问题。
4.volatile内存语义 & 内存屏障
volatile提供了一种轻量级的锁机制。volatile主要包含下面两重语义:
(1)保证修饰的变量(只能修饰成员变量),线程A对其修改能及时被其他线程感知
(2)禁止指令重排
volatile使用了内存屏障来禁止指令重排。
硬件级的内存屏障包括:
lfence,是一种Load Barrier 读屏障
sfence, 是一种Store Barrier 写屏障
mfence, 是一种全能型的屏障,具备ifence和sfence的能力
JVM则封装了内存屏障,提供下面4种内存屏障:
loadload:读屏障,load1 loadload load2,保证load2在load1之后执行
storestore:写屏障,store1 storestore store2,保证store2在store1之后执行
loadstore:读写屏障
storeload:写读屏障
我们也能使用Unsafe魔法类封装的方法来手动添加内存屏障。