JMM简介
Java Memory Mode,java内存模型,java多线程通信模型
涉及到维度:JAVA 层面 JVM层面 硬件层面
需要重点掌握理解 并发的三大特性,JMM工作内存和主存的关系,多线程是如何通信的,volatile保证可见性和有序性,CAS
并发和并行
并行:同一时刻,多条指令可以在多个处理器同时执行
并发:同一时刻,只有一条指令执行。多个线程之间快速轮动,交替执行
并发的三大特性
可见性 一个线程改变了共享变量的值,其他线程可以看到修改后的值
如何保证:
通过volatile关键字保证可见性
通过内存屏障保证可见性
通过synchronized关键字保证可见性
通过Lock保证可见性
通过final关键字保证可见性
还可以通过是内存中关键字失效来保证
有序性 程序执行顺序按照代码先后顺序执行,JVM存在指令重排,所以存在有序性问题
如何保证:
通过volatile关键字保证有序性
通过内存屏障保证有序性
通过synchronized关键字保证有序性
通过Locak保证有序性
原子性 一个或多个操作,要么全执行要不全部不执行
如何保证:
通过synchronized关键字保证原子性
通过Lock保证原子性
通过CAS保证原子性
并发编程的bug来源就来着可见性,有序性,原子性。
可见性
代码示例:
package com.example.demo.thread;
/**
* @author Fox
*
* -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp
* hsdis-amd64.dll
* 可见性案例
*
* 1.采用volatile修饰
* 2. int采用包装类Integer
* 3.采用内存屏障
* 4.synchronized()
*/
public class VisibilityTest {
// storeLoad JVM内存屏障 ----> (汇编层面指令) lock; addl $0,0(%%rsp)
// lock前缀指令不是内存屏障的指令,但是有内存屏障的效果 缓存失效
private boolean flag = true; //volatile
private int count = 0; //Integer
public void refresh() {
flag = false;
System.out.println(Thread.currentThread().getName() + "修改flag:"+flag);
}
public void load() {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
while (flag) {
//TODO 业务逻辑
count++;
//JMM模型 内存模型: 线程间通信有关 共享内存模型
//没有跳出循环 可见性的问题
//能够跳出循环 内存屏障
//UnsafeFactory.getUnsafe().storeFence();
//能够跳出循环 ? 释放时间片,上下文切换 加载上下文:flag=true
//Thread.yield();
//能够跳出循环 内存屏障
//System.out.println(count);
//LockSupport.unpark(Thread.currentThread());
//shortWait(1000000); //1ms
//shortWait(1000);
// try {
// Thread.sleep(1); //内存屏障
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//总结: Java中可见性如何保证? 方式归类有两种:
//1. jvm层面 storeLoad内存屏障 ===> x86 lock替代了mfence
// 2. 上下文切换 Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "跳出循环: count=" + count);
}
public static void main(String[] args) throws InterruptedException {
VisibilityTest test = new VisibilityTest();
// 线程threadA模拟数据加载场景
Thread threadA = new Thread(() -> test.load(), "threadA");
threadA.start();
// 让threadA执行一会儿
Thread.sleep(1000);
// 线程threadB通过flag控制threadA的执行时间
Thread threadB = new Thread(() -> test.refresh(), "threadB");
threadB.start();
}
public static void shortWait(long interval) {
long start = System.nanoTime();
long end;
do {
end = System.nanoTime();
} while (start + interval >= end);
}
}
以上列举的好多方法,都能保证可见性。最常使用的是采用volatile关键字修饰,volatile不具有原子性。
当写一个volatile变量时,JMM会把该线程对应的本地内存中共享变量刷新到主内存中
当读一个volatile变量时,JMM会把该线程对应的本地内存中共享变量置为无效,去
主内存中获取共享变量
有序性
示例代码如下
package com.example.demo.jmm;
public class ReOrderTest {
private static int x=0,y=0;
private static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
int i=0;
while (true){
i++;
x=0;
y=0;
a=0;
b=0;
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
shortWait(20000);
a=1;
x=b;
}
});
Thread thread2= new Thread(new Runnable() {
@Override
public void run() {
shortWait(20000);
b=1;
y=a;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("第" + i + "次" + "x=" + x + ",y="+ y);
if(x==0&& y==0){
break;
}
}
}
public static void shortWait(long intval){
long start = System.currentTimeMillis();
long end;
do{
end = System.nanoTime();
}while (start + intval >=end);
}
}
执行多次,x,y的值不是固定的