(二)线程------线程通信原理JMM模型

来源:互联网 发布:js如何获取span的值 编辑:程序博客网 时间:2024/05/23 20:25

简述:

    上一篇文章中我们学习了Java的内存模型,那么现在我们思考一个问题:如果程序中存在多个线程,他们是怎么进行数据通信的呢?OK,带着这个问题我们来学习本节内容---线程通信原理。
    本篇博文主要讲解:
     ① 什么是可见性、原子性、有序性。
     ② JMM模型。

JMM简介:

     JMM:Java Memory Model(Java内存模型),围绕着在并发过程中如何处理可见性、原子性、有序性这三个特性而建立的模型。OK,我们看一下三种特征。
    原子性(atomicity): 原子性是指一个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态 。由Java内存模型来直接保证原子性变量操作包括read, load, assign, use, store和write。大致可以认为基本数据类型的访问读写是具有原子性的。如果应用场景需要一个更大范围的原子性保证,Java内存模型还提供了lock和unlock操作来满足需求,尽管虚拟机没有把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,这两个字节码指令反映到Java代码中就是同步块——synchronized关键字,因此在synchronized块之间的操作也具备原子性。
    可见性(visibility): 可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型是通过在变量修改后将新值同步到主内存,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是,volatile的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说volatile保证了多线程操作时变量的可见性,而普通变量不能保证这一点。
    除了volatile之外,Java还有两个关键字能实现可见性,即synchronized和final。同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中”这条规则获得的,而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把"this"的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见final字段的值。
    有序性: Java程序天然的有序性可以总结为一句话:如果本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。JMM模型提供了volatile和synchronized来保证线程之间操作的有序性。
    总之,JMM模型就是提供一套机制来保证操作的原子性、可见性和有序性。根据数据是否被线程可见将JVM将内存组织为主内存和工作内存两个部分。主内存中主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程的栈和对主存部分变量拷贝的寄存器:
                  
    通过上面一张图我们清晰的分析线程1与线程2通信实际上通过主内存(Main Memory)实现的。比如,线程1自己工作线程内存(Working Memory)中的变量int a的副本刷新到主内存,然后线程2先将自己工作内存中的变量副本置为无效再从主内存中重新获取变量副本的值,这样就实现了两个线程间的通信。这也就是线程间通信的原理。下面在详细说一下主内存和工作内存:
主内存和工作内存:
  Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程里面的变量有所不同步,它包含了实例字段、静态字段和构成数组对象的元素,但不包含局部变量和方法参数,因为后者是线程私有的,不会共享,当然不存在数据竞争问题(如果局部变量是一个reference引用类型,它引用的对象在Java堆中可被各个线程共享,但是reference引用本身在Java栈的局部变量表中,是线程私有的)。为了获得较高的执行效能,Java内存模型并没有限制执行引起使用处理器的特定寄存器或者缓存来和主内存进行交互,也没有限制即时编译器进行调整代码执行顺序这类优化措施。
  JMM规定了所有的变量都存储在主内存(Main Memory)中。每个线程还有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。

总结:

    ① 线程间通信是通过共享主内存的方式去实现的。
    ② 线程间对共享数据操作时必须具备原子性、可见性和有序性。
    ③ 为了保证对主内存操作数据时不同步也为了操作的方便性,Java提供了Synchronized保证操作的原子性、可见性和有序性。提供了volatile可以保证操作的可见性。

0 0