系统学习java高并发系列三

转载请注明原创出处,谢谢!

首先需要说说线程安全?关于线程安全一直在提,比如StringBuilder和StringBuffer有什么区别? 经常就会出现关于线程安全与线程非安全,可能一直在提自己没有细细想想,如果忽然问你啥是线程安全的概念?可能你需要短暂停顿几秒,线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据,其实关于线程安全的定义我想不到好的,百度了下,也没有发现一个特别好的解释,我就选择一个相对来说还可以的解释吧 ,线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。我觉得该描述也不完全正确,因为现在控制并发的策略很多不仅仅是加锁机制,也可以不用加锁,我觉得这样可能比较合适的解释,就是多个线程都会操作到的,是一个公共资源或者共享的数据,但是每次操作只能一个线程使用而一旦临界区资源被占用其他的线程必须等待该资源的释放,在并行程序中,临界区资源都是受保护的那么就是线程安全,不包含的就是线程不安全的。

由于并发程序要比串行程序复杂很多,一个最重要的原因就是并发程序下访问的一致性和安全性将会受到严重挑战,如何保证一个线程可以看到正确的数据呢?因此我们需要深入了解并行机制的前提下,在定义一些规则来保证多线程直接有效的,正确的协同工作,而Java内存模型(JMM)就是来做这些事情的。

JMM模型都是围绕着多线程的原子性、可见性、和有序性来说的。这块内容过于复杂,自己水平有些,可能理解的有些偏差,希望到时候大家帮忙指出来。

原子性是指一个操作是不可中断的,即使在多线程一起执行的时候,一个操作如果开始就不会别其他线程干扰到。原文是:

Atomicity

  • Accesses and updates to the memory cells corresponding to fields of any type except long or double are guaranteed to be atomic. This includes fields serving as references to other objects. Additionally, atomicity extends to volatile long and double. (Even though non-volatile longs and doubles are not guaranteed atomic, they are of course allowed to be.)

  • Atomicity guarantees ensure that when a non-long/double field is used in an expression, you will obtain either its initial value or some value that was written by some thread, but not some jumble of bits resulting from two or more threads both trying to write values at the same time. However, as seen below, atomicity alone does not guarantee that you will get the value most recently written by any thread. For this reason, atomicity guarantees per se normally have little impact on concurrent program design.

long型字段和double型字段在32位hotspot可能不是原子性的,该如何证明呢?(作为一个思考题后续章节会进行解答,并且附上程序说明),在64位hotspot下面long型字段和double型字段都是原子性的。如果32位hotspot下volatile long 和volatile double也具有原子性 。为什么在32为hotspot加了volatile long型字段和double型字段字段就一定具有原型性了呢?,这与volatile的特性有关,当我们声明共享变量为volatile后,对这个变量的读、写会很特别,理解volatile的好方法就是把对volatile变量的单个读写堪称使用同一
锁对这些单个读写操作做了同步,锁的语义决定了临界区代码的执行具有原子性,所以在32为hotspot加了volatile long型字段和double型字段字段就一定具有原型性了,后续还会说volatile的,很难理解的关键词。

有序性,如果不是为了优化,为了性能,一般代码的执行顺序就是我们写的顺序从先到后一行一行执行,但是为了提高性能,我们需要优化,可能就会修改这些原先的顺序了,

目前的编译器和处理器常常会对指令做重排的。

  1. 编译器优化的重排序。编译器在不改变单线程语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器换用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序中执行。上述的1属于编译器重排序,2和3属于处理器重排序。

这些重排序可能会导致多线程程序出现内存可见性问题。对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,通过内存屏障指令来禁止特定类型的处理器重排序。关于内存屏障后续等深入了解了在来聊聊。

可见性是指当一个线程修改了某个共享变量的值,其他线程是否能够立即知道关于这个值的修改,可见性是一个复杂的综合性的问题,有一些关于缓存优化或者硬件优化会导致可见性的问题之外,上面提到的关于指令重排也会影响到可见性问题。

还有几个概念介绍:

happens-before简介

从JDK 5开始,Java使用新的JSR-133内存模型(除非特别说明,本文针对的都是JSR-133内存模型)。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
与程序员密切相关的happens-before规则如下:

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

注意:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。happens-before的定义很微妙,一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来说,happens-before规则简单易懂,它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法。

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。数据依赖分为下列3中类型:

  1. 写后读,a=1;b=a;写一个变量之后,再读这个变量。
  2. 写后写,a=1;a=2;写一个变量之后,再写这个变量。
  3. 读后写,a=b;b=1;读一个变量之后,再写这个变量。

上面3种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。
这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。

as-if-serial语义

as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

关于final和volatile后续章节介绍,很复杂很综合性的知识,今天这些内容很难理解,希望对大家有所帮助。


个人公众号

匠心零度公众号.jpg

posted @ 2017-09-25 19:00  匠心零度  阅读(884)  评论(4编辑  收藏