volatile 关键字的深入分析及AtomicInteger的使用

idgq

volatile的语义:
1、保证被volatile修饰的变量对所有其他的线程的可见性。
2、使用volatile修饰的变量禁止指令重排优化。
看代码:

public class InheritThreadClass extends Thread{
    private static volatile int a = 0;
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            a++;
        }
    }
    public static void main(String[] args) {
        InheritThreadClass[] threads = new InheritThreadClass[100];
        for(int i=0; i < 100; i++){
            threads[i] = new InheritThreadClass();
            threads[i].start();
        }
        //等待所有子线程结束
        while(Thread.activeCount() > 1){
            Thread.yield();
        }
        
        //这段代码会在所有子线程运行完毕之后执行
        System.out.println(a);  //(1)
    }
}

上面的代码中创建了100个线程,然后在每个线程中对变量a进行了1000次的自增运算,那么也就意味着,如果这段代码可以正确的并发运行,最后在代码(1)处应该输出100000。但是多次运行你会发现每次输出的结果并不是我们预期的那样,而都是小于等于100000。也就是说每次运行的结果是不固定的不一样的,这是为什么呢? 因为通过上面volatile关键字的语义我们知道被该关键字修饰的变量对所有的线程是可见的啊,那怎么会出现这种情况呢?难道语义有错? 那是不可能的,语义肯定是没有错的。

我们知道每一个线程都有自己的私有内存,而线程之间的通信是通过主存来实现的,volatile在这里保证多线程的可见性的意思是说:如果一个线程修改了被volatile关键字修饰的变量,会立马刷新到主内存中,其他需要使用这个变量的线程不在从自己的私有内存中获取了,而是直接从主内存中获取。虽然volatile关键字保证了变量对所有线程的可见性,但是java代码中的运算操作并非原子操作。

我们使用javap命令查看字节码(javap -verbose InheritThreadClass.class)会发现在虚拟机中这个自增运算使用了4条指令(getstatic, iconst_1, iadd, putstatic)。 当getstatic指令把a的值压入栈顶时,volatile关键字保证了a的值此时是正确的,但是在执行iconst_1iadd这些指令时其他线程有可能已经把a的值加大了,而已经在操作栈顶的值就变成了过期的数据了,所以putstatic指令执行后可能又把较小的a值同步回主内存了。 所以它不是一个原子运算,因此在多线程的情况下它并不是一个安全的操作。其实这么说也不是最严谨的,因为即使经过编译后的字节码只使用了一条指令进行运算也不代表这条指令就是原子操作。因为一条字节码指令在解释执行时,解释器需要运行许多行代码才能实现该条指令的语义,而即使是编译执行,一条字节码指令也可能需要转化成多条本地机器码指令。

所以有关volatile的变量对其他线程的”可见性“的语义描述并不能得出这样的结论:基于volatile变量的运算在高并发下是安全的。

那这种在高并发下的自增运算如何做到线程安全呢?可以使用synchronized,但是加锁的话性能开销太大,高并发下不是一个明智之选。可以使用并发包java.util.concurrent.atomic下的AtomicInteger原子类。
看代码:

    private static volatile AtomicInteger a = new AtomicInteger(0);
    
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            a.getAndIncrement();
        }
    }

上面的代码就可以在高并发下正确的运行,每次输出都是100000。
看AtomicInteger源码:

**//部分关键字段**
private static final Unsafe unsafe = Unsafe.getUnsafe();
/*
  valueOffset这个是指类中相应字段在该类的偏移量, 在下面的静态块中调用objectFieldOffset()方法初始化。
*/
private static final long valueOffset;

static {
  try {
    valueOffset = unsafe.objectFieldOffset
        (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;

//objectFieldOffset方法是一个本地方法
public native long objectFieldOffset(Field field);

// AtomicInteger的构造器之一
public AtomicInteger(int initialValue) {
    value = initialValue;
}
//getAndIncrement()这个方法的源码实现
public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}
//get()方法的实现
public final int get() {
    return value;
}
/*compareAndSet(int expect, int update)方法内部又直接调用了
 *unsafe的compareAndSwapInt方法,这里直接上compareAndSwapInt源码的实现
 *在obj的offset位置比较内存中的值和期望的值,如果相同则更新。
 *这是一个本地方法,应该是原子的,因此提供了一种不可中断的方式更新
*/
public native boolean compareAndSwapInt(Object obj, long offset,  
                                            int expect, int update); 
    
阅读 5.2k

idgq
任何时候开始都不晚,晚的是不开始!!
569 声望
12 粉丝
0 条评论
569 声望
12 粉丝
文章目录
宣传栏