java内存模型与线程

2020-01-07  本文已影响0人  MadnessXiong

1. Java内存模型

1. 硬件的效率与一致性:
计算机进行的绝大多数任务不可能只靠处理器计算就能完成,处理器还要与内存交互,如读取运算结果,存储运算结果。但是由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现在计算机都不得不加入一层读写速度尽可能接近处理器运算速度的告诉缓存来作为内存与处理器之间的缓冲:将运算需要用到的数据复制到缓存中,让运算能快速进行,当运算结束后,再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。

基于高速缓存的存储很好地解决了处理器与内存的速度矛盾,但它引入了一个新的问题:缓存一致性。在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存。

那么当多个处理器的运算都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致,那么同步回主内存的数据以谁的为准呢?所以各个处理器访问缓存时都要遵循一些协议。如下图:


1578224567753.jpg

2. java内存模型:
java内存模型规定了所有变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不用线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。线程,工作内存,主内存三者交互关系如下图:

1578229091699.jpg

这里的变量包括实例字段,静态字段和构造数字元素的对象,但不包括局部变量与方法参数,因为后者是线程私有的,不会存在竞争问题。

从变量,主内存,工作内存的定义来看,主内存主要对应Java堆中的对象数据部分,而工作内存则对应虚拟机栈中的部分区域。

3. 内存间交互操作:
即一个变量如何从主内存拷贝到工作内存中,如何从工作内存中同步回主内存之类的细节,java内存模型定义了以下8种操作来完成。
以下每一种操作都是原子的,不可再分的(double和long类型在某些平台下可能会有例外,但不影响,不必关注)

Java内存模型只要求read和load,store和write,这2个操作之间必须按顺序执行,而没有保证是连续执行。它们之间是可以插入其他指令的,如:reada,readb,loadb,loada。

java内存模型还规定了在执行上述8中基本操作时必须满足如下规则:

所以java中的变量必须都经过初始化,类中的变量在加载过程中会给一个默认值,方法体中的变量必须手动给一个默认值

4. 指令重排 原子性,可见性,有序性:

            //线程A
            doSomeThing();//语句1
            isDoSomeThing=true;//语句2
            //线程B
            if (isDoSomeThing){
                doSomeThingElse();
            }

为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序的结果重组,保证该结果与顺序执行的结果是一致的,但不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。

所以在以上代码中,线程A做完一些操作,然后更新标记isDoSomeThing,如果线程B的doSomeThingElse()要依赖线程A的doSomeThing()的执行结果,但是由于指令重排的原因,线程A的语句2先执行了,语句1还尚未执行,那么线程B这里的doSomeThingElse(),就会出错。

除了volatile之外,Java中synchronized和final也能实现可见性。synchronized的可见性是由“对一个变量执行unlock操作之前必须先把此变量同步回主内存中,这条规则获得的”。被final修饰的字段在构造器中一旦初始化完成,那么在其他线程就能看见final字段的值。(final这块不是很明白)

5. volatile关键字

当一个变量定义为volatile之后,它将具备2种特性,第一是保证此变量对所有线程的可见性,不能保证原子性。由于volatile变量只能保证可见性,在不符合以下2条规则的运算场景中,我们仍然要通过加锁来保证原子性。

2. Java线程

1. 线程同步的实现:

如果一般的多线程情况则直接使用synchronized即可,如果需要用到ReentrantLock功能的再使用ReentrantLock。在性能方面,jdk1.6发布后,通过锁优化他们的性能基本持平了

2. 锁优化

上一篇 下一篇

猜你喜欢

热点阅读