理解JMM
理解JMM
volatile 是java虚拟机提供轻量级的同步机制,是一个java关键字
1.保证可见性
2.不保证原子性
3.禁止指令重排
JMM java 内存模型,是一种约定,不是真实存在的。
关于JMM的一些约定
1.线程解锁前,必须把共享变量刷回主存
2.线程加锁前,必须读取主存的最新值到工作内存中
3.加锁和解锁必须是同一把锁
JMM规定,内存主要划分为主内存、工作内存两种。主内存对应的是java堆中的对象实例部分,工作内存对应的是栈中的部分区域。每条线程拥有各自的工作内存,是主内存的一份拷贝。
8种操作(均为原子性操作)
lock: (锁定)作用于主内存的变量,把一个变量标识为线程独占状态
unlock:(解锁)作用于主内存的变量,把一个线程独占状态的变量释放,可以被其他线程占用
read:(读取)作用于主内存变量,将主内存变量读取到缓存中
load:(载入)作用于工作内存变量,将缓存中的变量写入到工作内存中
use:(使用)作用于工作内存变量,将工作内存中的变量传输给执行引擎
assign:(赋值)作用于工作内存变量,将执行引擎中的变量传输给工作内存
store:(存储)作用于工作内存变量,将工作内存变量读入缓冲区
write:(写入)作用于主内存变量,将工作内存变量读入主内存
原则:
必须成对出现
一、保证可见性
package com.example.juc;
import java.util.concurrent.TimeUnit;
public class TestVolatile {
private static volatile int num = 0; //不加volatile程序会死循环,加了volatile保证了可见性
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (num == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
}
二、不保证原子性
demo
package com.example.juc;
public class TestVolatile2 {
private volatile int num = 0;
public static void main(String[] args) {
TestVolatile2 testVolatile2 = new TestVolatile2();
testVolatile2.test();
}
public void test() {
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(num);
}
public void add() {
num++;
}
}
16579
add方法设置为synchronized
package com.example.juc;
public class TestVolatile2 {
private volatile int num = 0;
public static void main(String[] args) {
TestVolatile2 testVolatile2 = new TestVolatile2();
testVolatile2.test();
}
public void test() {
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(num);
}
public synchronized void add() {
num++;
}
}
20000
不使用synchronied 和lock如何保证原子性?
使用JUC下的Atomic包,该包下面使用了Unsafe类,在CAS里面详细讲
package com.example.juc;
import java.util.concurrent.atomic.AtomicInteger;
public class TestVolatile3 {
private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
TestVolatile3 testVolatile2 = new TestVolatile3();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
testVolatile2.add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(num);
}
public void add() {
num.getAndIncrement();
}
}
三、禁止指令重排
什么是指令重排?你写的程序,计算机并不是按照你写的那样去执行的,会进行优化重排
源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
处理器在进行指令重排的时候会考虑指令的依赖性!
1.不影响结果的重排
int a =0; // 1
int b = 0; // 2
a = a + 3; // 3
b = b + 5; // 4
我们认为计算器执行的是1,2,3,4
实际执行可能是1324 2134 2143
但是不可能是4213
2.影响结果的重排
x,y,a,b初始值均为0
线程A | 线程B |
---|---|
x = a | y = b |
b = 2 | a=1 |
result: x=0,b=2,y=0,a=1
指令重排后,结果会变化
线程A | 线程B |
---|---|
b = 2 | a=1 |
x = a | y = b |
result: x=1,b=2,y=2,a=1
volatile关键字可以避免指令重排!
利用CPU指令中的内存屏障
内存屏障的作用:
1.保证特定操作的执行顺序
2.可以保证某些变量的内存可见性(利用这些性质volatile实现了可见性)
volatile运用的比较多的就是单例模式!