美文网首页
java并发编程要点

java并发编程要点

作者: Ronry | 来源:发表于2017-07-16 09:56 被阅读0次

Java并发问题主要有三个核心概念:原子性,可见性,顺序性。

原子性

并发问题的原子性的概念和数据库事务的原子性是一样的:一个操作的多个步骤要么一起生效,要么一起回滚。
java自带的并发控制的手段中,可以保障原子性的有:

  • synchronized 关键字
  • CAS
可见性

可见性的问题是指当变量被多个线程使用时,一个线程对其作出的修改操作,是否后续其他线程读的时候能马上读到修改后的值。可见性问题的产生是因为CPU为了加速数据读取读取的速度,采用了多级的告诉缓存。
java中保障可见性的手段有

  • synchronized关键字
  • volidate关键字
顺序性

顺序指的是代码的执行顺序,顺序性指的是java编译器和CPU,在保障单个线程执行的时代码的逻辑结果不变的前提下,可能会对代码执行顺序的作出的一些改变(优化)。代码块在单线程执行的情况下,不管顺序怎么变,jvm一定会保证其逻辑不变,但是多个线程的情况下如果发生了重排序,则局部的逻辑可能是没变,单是全局的逻辑已经有问题了,比如下面的代码块

class BadlyOrdered {  
  boolean a = false;  
  boolean b = false;  
  void threadOne() {  
    a = true;  
    b = true;  
  }  

  boolean threadTwo() {  
    boolean r1 = b; // sees true  
    boolean r2 = a; // sees false  
    return r1 && !r2; // returns true  
  }  
}  

方法threadOne在单线程执行的情况下,不管顺序怎么变,其最终的结果都是a和b都赋值为true。但是如果是多个线程,线程1执行方法threadOne,线程2执行方法threadTwo,即使不考虑可见性的问题,因为threadOne的执行顺序被排序,所以b的值得先被赋值为true,然后线程2读到了b=true和a=false,最后返回的结果将是true。
那么怎么解决多线程情况下,顺序性带来的不确定问题呢。一种方法是在编码时显示使用synchronized关键,做到同一时间只有1个线程能操作共享的数据。另外一个方法运用jvm自带的happens-before规则。

happens-before

happens-before规则的描述如下

Happens-Before Relationship : Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.

可见,happens-before规则同时对可见性和顺序产生的了影响。happens-before这个名称感觉还是挺有歧义的,从"before"这个字眼上看,以为它定义的是顺序性,其实"before"描述的是可见性的"before"。而顺序性,并不能简单的从字面描述去理解。比如,java规范定义了多条满足的happens-before规则,有一条是:

A write to a volatile field happens-before every subsequent read of that volatile.

它的"before"的定义,不能简单理解为对一个volatile变量的写操作发生在对其读操作之前。一个线程写,一个线程读,jvm可不知道哪个会先执行到。这里的"before"是可见性的before,也就是,一个volatile变量的写操作以及其后面的操作,对另外一个线程的发生在这之后的volatile变量的读操作以及之后的操作可见。
而这条规则,对顺序性的真正的影响是:

  • 禁止把volatile写之前的行为与它重排序
  • 禁止把volatile读之后的行为与它重排序

对于上面的示例程序,只要把变脸b用volatile关键字修饰,那threadOne方法就不会发生重排序了。也就是说happens-before规则也规定哪些类型的操作之间,编译器和CPU不能对其进行重排序,编程的时候只要利用这些内置的规则,就能消除重排序在多线程环境下的问题。
总结来说,顺序性是java为了单线程执行的时候做的优化,但是它会在多线程环境下带来问题,并且这些问题基本是不能在编码阶段做理论分析的。为了解决这个问题,java又自己内置了一些规则(happens-before规则)对重排序做了一些限制,多线程编程的时候,只要利用这些规则,确保我们关心的关键点不会发生重排序,也就保证了程序的正确性。

Lock接口

我们知道Lock是jdk提供的并发控制工具包,使用Lock也能保证java并发的三个重要特性的。但是需要说的Lock只是一个工具包,它提供的保障都是依赖jvm提供的更底层的机制来保障的

  • 可见性: 是对实现中的锁变量使用volidate关键字,然后依赖volidate带来的happen-before特性,保障加锁和解锁之间的修改对其他线程可见的
  • 原子性:Lock本身的加锁解锁这两个行为的原子性是通过CAS操作保障的,而加锁之后的行为的原子性,是通过控制同一时间只能由1个线程执行,逻辑保障的
  • 顺序性: 同原子性,也是通过控制同一时间只能由1个线程执行的保障的
总结
原子性 可见性 顺序性
sychronized 保证 保证 保证
volatile 不保证 保证 部分保证
CAS操作 保证 不保证 不保证
Lock工具类 保证 保证 保证

####### refer
http://www.infoq.com/cn/articles/java-memory-model-2
http://ifeve.com/easy-happens-before/
http://blog.csdn.net/admiral_dota/article/details/50489882

相关文章

网友评论

      本文标题:java并发编程要点

      本文链接:https://www.haomeiwen.com/subject/kqjahxtx.html