一个volatile关键字i,10个线程每个线程执行1000次i++后i的结果(微众银行一面)
volatile不保证原子性
public class TestVolatile {
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
testVolatile.increase();
}
}
}.start();
}
//这里为了保证上面的线程执行完再打印结果
Thread.sleep(3000);
System.out.println(count);
}
public void increase() {
count++;
}
}
发现怎么测试,结果都小于等于10000.原因是volatile不保证原子性。最容易想到的就是给increase方法加锁,当然还有种方法是用AtomicInteger的getAndIncrement()方法
加锁
public synchronized void increase() {
count++;
}
使用AtomicInteger的getAndIncrement()方法
public class TestVolatile {
private static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
TestVolatile testVolatile = new TestVolatile();
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
testVolatile.increase();
}
}
}.start();
}
Thread.sleep(3000);
System.out.println(count);
}
public void increase() {
//getAndIncrement()可保证原子性
count.getAndIncrement();
}
}
点进getAndIncrement()方法的源码,可以看到getAndIncrement()底层调用了native类unsafe.getAndAddInt(),那getAndAddInt是如何保证原子性的呢?往下看:
public final int getAndIncrement() {
//this是当前对象,valueOffset是内存偏移量,直接从内存地址拿到变量值, 然后将该变量值加1
return unsafe.getAndAddInt(this, valueOffset, 1);
}
点开getAndAddInt方法,可以看到是用的do while循环(自旋锁)+ CAS(Compare And Swap)来保证原子性
public final int getAndAddInt(Object obj, long offset, int delta) {
int v;
do {
//通过对象和偏移量获取变量的值
v = this.getIntVolatile(obj, offset);
/*
while中的compareAndSwapInt方法尝试修改v的值,具体地, 该方法也会通过obj和offset获取变量的值
如果这个值和v不一样, 说明其他线程修改了对应改地地址处的值, 此时compareAndSwapInt返回false, 继续循环
如果这个值和v一样, 说明没有其他线程修对应改地址处的值, 此时可以将对应改地处的值改为v+delta(该变量值加1), compareAndSwapInt返回true, 则退出循环
Unsafe类中的compareAndSwapInt方法是原子操作, compareAndSwapInt方法不会被其他线程中断
*/
} while(!this.compareAndSwapInt(obj, offset, v, v + delta));
return v;
}