1. 简介
我常常使用synchronized和volatile在我们的程序中保证在并发中数据的安全,但是我们怎么理解他们了??
2.JMM(Java Memory Model)
JMM 是一个JVM对主内存和缓存和CPU之间的关系做了简化抽象,在实际情况下并不存在.在下图 ThreadStack 对应缓存和CPU的寄存器,而Heap对应主内存
image.png
3. 异同
- Synchronized 是修饰代码块和方法的,而volatile是修饰变量的
- Synchronized 是保证原子性和有序性和可见性,而volatile只有 有序性和可见性
3 Synchronized 会导致线程上下文切换,而 volatile不会,这就会使volatile的效率高于synchronized- synchronized是在读入变量的时候,会清空当前线程的缓存,直接从主内存中读取,而volatile是在其中有一个线程进行写操作结束的时候,会通知其他线程他们缓存失效,需要从主内存中读取,用volatile修饰的变量所有的写操作都优先于读操作。(任何变量进行写操作的时候都会写入主内存,而读取就优先从缓存中读取)
4 一个简单的例子
这个例子我们会在多线程下,执行对同一个变量进行修改和读取
4.1 我对写操作使用synchronized 和读操作不使用,变量也不用volatile修饰
package liusheng.main.liusheng.main;
import liusheng.main.ConcurrentQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
public class SynAndVolatile {
static class TestClass {
int num = 0;
public synchronized void add() {
num++;
}
public int getNum() {
return num;
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(20);
TestClass testClass = new TestClass();
IntStream.range(0, 10).boxed().map(i -> (Runnable) () -> {
for (int j = 0; j < 10000; j++) {
testClass.add();
}
System.out.println("完成");
}
).forEach(executorService::execute);
for (; ; ) {
if (testClass.getNum() == 100000) {
System.out.println("SUCCESS");
}
}
}
}
4.1 结果
image.png
我们发现程序卡主了,读取不出num的值,也就是main线程是在缓存中读取的,不是在主内存中读取的,所以发现不了num值的变化
4.2 我们对num进行volatile修饰,其他的还是一样的
volatile int num = 0;
4.2 结果
我们发现主内存检查到了num值发生的变化,当num值发生变化的时候会通知其他(含有num副本的现场)线程,这num副本失效,需要从主内存从新读取。