浅谈Java内存模型、并发、多线程
Java内存模型(Java Memory Model)是围绕着在并发编程中如何处理原子性,可见性,有序性三个特性而建立的模型。
下面我简单描述一下这三个特性:
原子性:
通俗一点来说,就是要不全部做,要不全部不做。
可见性:
当A线程对某变量进行修改时,需要立即写入主存中,B线程再去主存中就能读取到最新的值,在Java中就是利用volatile来保证可见性的。
有序性:
为了提升性能,JMM是允许编译器和处理器对指令重排序的(只要结果一样,JMM就允许重排序)。因为JMM规定了as-if-serial语义,不管指令怎么重排序,程序的执行结果都不会改变,这里再谈一点,如果重排序没有遵循as-if-serial语义的话会发生什么?下面我贴图出来。
比如有这样一段代码:
int a = 0;bool flag = false;public void write() {a = 2; //1flag = true; //2}public void multiply() {if (flag) { //3int ret = a * a;//4} }
你以为会是1 2 3 4的执行顺序执行下来,但其实不是。
像下图一样,两个线程按照这样的顺序执行了,最后得到的结果那将不一样。这里如果展开说的话也可以聊一下多线程的synchronized和Lock。
另外,JMM具备一些先天的有序性,即不需要通过任何手段就可以保证的有序性,通常称为happens-before原则。
现在我们来聊一聊volatile关键字。
按照我自己和参照网上的意思,大概就是分成两点:
1 . 保证了不同线程对该变量操作的内存可见性
2 . 禁止指令重排序
这里又谈到了内存可见性和重排序,记住几点,只要被volatile关键字修饰的共享变量,每次针对该变量的操作都激发一次load and save,这样才保证了内存的可见性。
如图所示,线程会先从主存中读变量的值,然后加载到工作内存中,处理器处理后再传到工作内存副本中,再写进主存中去,这就涉及到原子性,要不全部做完,要不全部不做。
假设A线程从主存中拿出一个变量值为0做了+1,修改完只写到了工作内存中,然后B线程又拿了该变量出来做了+1操作,现在A线程将该变量存到主存中,B线程也将该变量存进主存中,你说这个时候变量值是几?
所以JMM就是围绕着在并发编程中如何处理原子性,可见性,有序性三个特性而建立的模型,通过解决这三个问题,可以解决缓存不一致的问题。而volatile跟可见性和有序性都有关。
请注意!这里我可没说volatile一定保证了原子性。在某些情况下是可以保证原子性,某些情况下又不可以,如果是复合操作的话(例如volatile++),则不能保证它的原子性。例如:
public volatile int num = 0;
如果num做自增,假设A线程进来,自增到了5,这个时候发生了阻塞,没有自增,所以也没有触发volatile规则,这个时候如果B线程再进来拿到num,再做了自增,变成了6再写到主存中。A线程恢复继续从工作内存中拿5开始自增,得到了6再写到主存中去。想想是不是哪里不对劲?是的,两个线程对num都进行了自增,但是只得到了一次的结果。这就不是我们想要的结果了!这时候就需要给线程们加上线程锁或者其他等等。
并发编程与多线程是我们开发中常遇见的问题,因为是第一次写,也参照了很多前辈的博客,多担待。有错请务必指出,转载请注明出处谢谢。