1. 开始
仔细说volatile是一个复杂的问题,可以从Java内存模型聊到缓存一致性协议,很难界定学到什么地方为止。
很多时候,我们并不需要那么复杂,我们需要更加实用。
所以,下面我们就来聊聊volatitle在实际开发中的问题。
2. 并发编程中的三个重要概念
- 原子性:是指一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
- 可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
- 有序性:是指程序执行的顺序按照代码的先后顺序执行
volatitle只保证:可见性和有序性
什么意思?下面简单聊聊。
3. 可见性
Java内存模型中,JVM会为每一个线程分配一个本地缓冲区(TLAB,Thread Local Allocation Buffer)。
Java线程会现在自己的缓冲区内修改数据,然后同步到主内存中。
有缓存就可能存在数据一致性问题。
和我们通过网页修改同一份文档是一个道理,我们怎样保证自己修改的东西别人没有修改?
一个典型的代码例子如下:
private boolean init = false;
public void init() {
init = true;
}
public void doSomething(){
if(init){
System.out.println("dosomething");
}
}
一个线程调用实例方法init修改了init成员变量,但是这个修改是在自己的内存中进行,如果有另一个线程调用doSomething,它的缓冲区中缓存了init变量,它看不到其他线程修改。
同样和在线多人编辑同一文档一样,在没有提交之前,每个人都不知道其他人修改了什么。
如果加上volatitle关键字则不同:
private volatitle boolean init = false;
每个线程在修改了volatitle变量之后,都会马上把这个修改同步到主内存,每个线程在读取volatitle变量的时候都会先去主内存看这个变量有没有被其他变量修改。
4. 有序性
为了获得较好的执行性能,Java没有限制编译器对指令进行重排序。
啥意思?就是下面的方法中,编译之后,init=true可能比a=1先执行。重排序规则又是一个复杂的问题,这里我们只需要知道,指令可能存在重排序。
private int a = 0;
private boolean init = false;
public void init() {
a = 1;
init = true;
}
那么问题来了:
public class VolatitleTest {
private int a = 0;
private boolean init = false;
public void init() {
a = 1;
init = true;
}
public void calc() {
if (init) {
System.out.println(a * a);
}else {
System.out.println(-1);
}
}
}
如果多个线程,同时调用同一个VolatitleTest实例的init,calc方法的结果就不唯一。
重排序这个问题不好复现,但是我们需要知道这个问题的确存在,最典型的例子就是单例模式:
public class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
Singleton必须使用volatitle关键字,否则是有问题的,因为类的创建是多个步骤,有兴趣的朋友可以直接研究一下。
5. volatitle不保证原子性
最重要的问题:volatitle不保证原子性
典型的例子是:
private volatile int a = 0;
public void increace(){
a++;
}
尽管成员变量使用volatitle修饰,但是increace不是线程非安全的,通常它累加出来的值比实际值小,所以如果要求是精确值得使用CAS或者synchronized。
例如,JDK的AtomicXXX都是使用CAS的方式:
private volatile int value;
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
Tomcat中的实现,基本使用的synchronized方式:
protected volatile int maxActive=0;
@Override
public void add(Session session) {
sessions.put(session.getIdInternal(), session);
int size = getActiveSessions();
if( size > maxActive ) {
synchronized(maxActiveUpdateLock) {
if( size > maxActive ) {
maxActive = size;
}
}
}
}