原子性:即不可分割性,不发生上下文切换,线程调度器在未执行完毕前不会打断它,切换到其他线程中。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。
可见性:一个线程对共享变量的修改,另外一个线程可以及时地看到。
要实现共享变量的可见性,必须保证两点:
①线程修改后的共享变量能够及时地刷新到主内存中
②其他线程能及时把共享变量的最新值从主内存刷新到自己的工作内存中。
这里涉及到了Java Memory Model(JMM,Java内存模型)
Java内存模型
对于Java来说开发者并不需要关心任何硬件细节,因此没有多核CPU和高速缓存的概念。多核CPU和高速缓存在JVM中对应的是Java语言内置的线程和每个线程所拥有的独立内存空间,Java内存模型所规范的也就是数据在线程自己的独立内存空间和JVM共享内存之间同步的问题。
所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。
从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
可以看出,线程A要和线程B通信的话必须经过两个步骤:
①线程A把本地内存中更新过的共享变量的副本刷新到主内存中
②线程B从主内存中读取被线程A更新过的共享变量
线程A和线程B通信示意图如图
如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
以上可以知道,要实现共享变量的可见性,必须保证两点:①线程修改后的共享变量能够及时地刷新到主内存中
②其他线程能及时把共享变量的最新值从主内存刷新到自己的工作内存中。
happens-before
从JDK5开始,java使用新的JSR -133内存模型(本文除非特别说明,针对的都是JSR- 133内存模型)。JSR-133提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在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仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second),即它是对可视性的要求
单线程重排序和临界区(同步)内部重排序遵循as-if-serial语法,结果是一样的,临界区其实是和单线程一样。具体的可以参考:http://www.infoq.com/cn/articles/java-memory-model-3
同步和volatile能保证可视性,且同步还能保证原子性。对一个volatile变量的单个读/写操作,与对一个普通变量的读/写操作使用同一个监视器锁来同步,它们之间的执行效果相同。(因为同步有两条规定,恰好是volatile的效果)从内存语义的角度来说,volatile与监视器锁有相同的效果:volatile写和监视器的释放有相同的内存语义;volatile读与监视器的获取有相同的内存语义。
推荐使用synchronized,而不是volatile或者lock
3152

被折叠的 条评论
为什么被折叠?



