Java多线程之线程的可见性(二)

来源:互联网 发布:mac韩服lol汉化 编辑:程序博客网 时间:2024/05/19 13:09

1.Java的内存模型

Java Memory Model (JAVA 内存模型)描述线程之间如何通过内存(memory)来进行交互。 具体说来, JVM中存在一个主存区(Main Memory或Java Heap Memory),对于所有线程进行共享,而每个线程又有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。
这里写图片描述

2、内存可见性

从上图可知,如果线程A对共享变量X进行了修改,但是线程A没有及时把更新后的值刷入到主内存中,而此时线程B从主内存读取共享变量X的值,所以X的值是原始值,那么我们就说对于线程B来讲,共享变量X的更改对线程B是不可见的。
例如:

public class NoVisibility{    private static boolean asleep = true;    private static class readerThread extends Thread{        while(!asleep){            doSomething();        }    }    public static void main(String []args){        new readerThread().start();        asleep = false;    }}

当主线程把asleep改成true时,这个new readerThread()可能会继续循环下去,因为主线程的asleep的结果储存在他的本地内存中,而没有刷新到主内存中,所以new readerThread()读取主内存,根本没有察觉到asleep的变化。

3.如何实现可见?

实现可见有四类关键字,volatile,java.util.concurrent中实现的原子操作类,synchronized,还有final
 

volatile

volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。简单来说就是读必须从主内存读,写必须写到主内存中。

由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 就跟C中的一样 禁止编译器进行优化。

使用建议:
1.在上面的asleep状态变量的例子中,非常适合用volatile关键字,它用来检测某个状态变量以判断是否进行下一步操作。它常常用做中断或状态的标志

2.当要访问的变量已在synchronized代码块中,或者为final时,不必使用。原因很简单,因为synchronized和final都能让变量可见
 

synchronized

这里写图片描述

synchronized的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这条规则获得的。
 

Java.util.concurrent中实现的原子操作类

Java.util.concurrent中实现的原子操作类包括:
AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference。
 
其底层就是volatile和CAS 共同作用的结果:

  • 首先使用了volatile 保证了内存可见性。
     
  • 然后使用了CAS(compare-and-swap)算法 保证了原子性。
    其中CAS算法的原理就是里面包含三个值:内存值A 预估值V 更新值 B 当且仅当 V == A 时,V = B; 否则,不会执行任何操作。

 

final

被final修饰的字段是构造器一旦初始化完成,并且构造器没有把“this”引用传递出去,那么在其它线程中就能看见final字段的值。

原创粉丝点击