一,什么是JMM?
它是一种java内存中数据存储的的协议规则:规定了内存是所有线程共有的,每个线程都有自己的工作内存,当进程需要使用数据时会去内存中读取数据放入自己的工作内存,然后对工作内存的数据进行操作,最终将操作后的数据写回到内存中。
二,八大操作
其实就是读写数据的原子操作,保证数据的读写不出现问题。
1,read操作:将数据从堆栈中读出来到一个缓冲区A
2,load操作:将缓冲区A的数据写入到工作内存
3,use操作:线程读取工作内存的数据进行计算
4,assign操作:线程在CPU中执行,将计算好的值存储到工作内存中
5,store操作:将工作内存大数据写入缓冲区B
6,write操作:将缓冲区B的数据写入原有的为止,对其进行赋值操作
7,lock操作:将堆栈中的变量加锁,标识为内存独占状态
8,unlock操作:堆栈中的变量解锁,其它线程就可以锁定该变量
三,volatile
保证可见性(线程A修改的数据对线程B来说是可见的)
不保证原子性(例如a++实现的a+1操作其实是有多个步骤组成,他们是可分割的),synchronize保证代码操作原子性
保证有序性(代码的执行过程中,为了提高执行的效率会发生执行的重新排列)
1,可见性验证:
private static int flag = 0;
public static void main(String[] args) {
//主线程写一个死循环
new Thread(()->
{
while (flag==0)
{
System.out.println("a");
}
flag = 1;
}).start();
//终止死循环
flag=1;
System.out.println(flag);
//结果:无法终止死循环
//解决,使用volatile:private static volatile int flag = 0;
//说明volatile可以保证程序的可见性
}
2,验证volatile不保证原子性
private volatile static int sum = 0;
private volatile static AtomicInteger sum1 = new AtomicInteger(0);
public static void main(String[] args) {
//理论上的值为50000,但是由于volatile不能保证原子性,所以达不到50000
for (int i=0;i<50;i++)
{
new Thread(()->
{
for (int j=0;j<1000;j++)
{
//其实它在底层的步骤为:获取值,加1,写回值
sum++;
sum1.getAndIncrement();
}
}).start();
}
//保存计算的线程都执行完毕,留下的线程为main和gc
while (Thread.activeCount()>2)
{
Thread.yield();
}
System.out.println(sum);
System.out.println(sum1);
}
3,保证有序性,防止指令重排
什么是指令重排:我们写的程序真正执行是并不一定会按照我们认为的顺序执行的,为了提高程序的执行效率,
编译器优化时会发生重排,执行指令并行时会发生重排,内存系统也会发生重排
编译器在执行指令重拍时,会考虑数据间的依赖性
int y =1 //1
int y=2 //2
x=x+5 //3
y=x*x //4
我们期望执行的顺序:1234,最终执行的顺序可能是2134 1324,但不可能是4123,这由数据间的依赖性保证的
但是有时的指令重排是会导致数据出错的,此时如果加上了volatile关键字,就会在执行相应的命令组前后加上一组内存屏障,
在内存屏障中的指令就不会发生指令重排
内存屏障在单例模式中用的最多,单例模式使用枚举的方式创建是最适合的
public enum Singleton
{
INSTANCE;//该枚举类对象的实例
public void doSomething()
{
System.out.println("该枚举类实现的方法");
}
}
//调用的方式
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}