Java的可见性和原子性

1. 工作内存和主内存

  • 所有的变量都存在主内存中(一份)
  • 每个线程有自己独立的工作内存(主内存中该变量的一份拷贝)

2. 可见性和共享变量

  • 可见性:一个共享变量的值能够及时地被其他线程看到
  • 共享变量:如果一个变量在多个线程的工作内存中都存在拷贝,那么它就是这几个线程的共享变量

下图可以反映以上说明:

3. 约束

  • 线程对共享变量的操作只能在自己的工作内存中进行,不能直接修改主内存
  • 不同线程之间无法访问其他线程工作内存中的变量,必须通过主内存来访问其他线程工作内存中的变量

4. 共享变量可见性的原理

线程1对工作内存1的修改能及时让线程2及时可到,就是可见了。

4.1 可见性的步骤

  1. 将工作内存1中更新后的值写入主内存
  2. 主内存将最新的共享变量的值写入工作内存2

4.2 保证可见性的必要条件

  • 工作内存更新的共享变量值要及时写入主内存
  • 主内存及时更新共享变量的值写入工作内存2

synchronized实现可见性

5.1 synchronized实现的内容

  1. 原子性(同步)
  2. 可见性

5.2 JMM关于synchronized 的规定

  1. 解锁前必须把工作内存最新值写入主内存
  2. 加锁前清空工作内存的共享变量值,从主内存更新最新值

5.3 synchronized互斥代码的过程

  1. 获得互斥锁
  2. 清空工作内存
  3. 从主内存拷贝最新变量复制到自己的工作内存
  4. 执行代码
  5. 将更改的共享变量值写入主内存
  6. 释放互斥锁

6 volatile实现可见性

首先在说volatile实现可见性方法之前说一下编译重排序

6.1 重排序

编译器或处理器为了提高性能做优化,调整了执行顺序使得和书写顺序不同

6.2 as if serial语义

无论如何排序,程序执行结果与代码按照书写顺序执行的结果一致。在单线程情况下,遵循as if serial语义

volatile通过内存屏障和禁止重排序优化来实现可见性

  • 写操作后加入store屏障指令:写之后强行将更新的值写入主内存
  • 读操作前加入load屏障指令:读之前及时从主内存更新最新值

6.3 volatile无法实现原子性

  • 一般来说自增操作无法保证其原子性

解决方案:

  1. 同步块
  2. ReentrantLock(java.util.concurrent.locks) 3.AtomicInteger(java.util.concurrent.atomic)

多线程中安全使用volatile需要同时满足以下2点

  1. 对变量的写入操作不依赖当前值(比如自增、自减等操作;)
  2. 不能包含在一个不变式中(比如low<up,永远成立的)

7 synchronized和volatile的比较

  1. volatile不需要加锁,更加轻量级,不会阻塞线程
  2. 从内存可见性角度,volatile变量读等价于加锁,写等价于解锁
  3. synchronized可以同时保证原子性和可见性,而volatile一般来说只能保证可见性
  4. volatile需要注意使用的风险
  5. volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
  6. volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化 原文地址:http://blog.csdn.net/uniquewonderq/article/details/48477265

猜你喜欢

转载自jaesonchen.iteye.com/blog/2344407