多线程及并发面试基础(2)——volatile不保证原子性的解决方案

接上上一讲volatile的内容,我们说volatile有三个特性。其中一个不保证原子性。那么并发下面对num++之类简单运算且不用synchronized怎么实现呢?我们使用并发包下面的原子操作类。代码如下:

class myData{
  volatile AtomicInteger atomicInteger = new AtomicInteger(0);
    void add(){
        for (int i = 0; i < 1000; i++) {
            atomicInteger.getAndIncrement();
        }
    }
}

public class volatileTest {
    public static void main(String[] args) {
        myData1 myData = new myData1();
         for (int i = 0; i<10 ; i++) {
             new Thread(()->{
                 myData.add();
             },String.valueOf(i)).start();
         }
        System.out.println(myData.atomicInteger);
    }
}

输出结果:10000

1、思考一个问题,为什么使用原子引用类AtomicInteger之后就可以保证原子性?

保证原子性操作一般来说就是加锁了,控制这个变量再被修改的时候不允许别的线程同时去修改这个变量。那么原子引用类在对数据操作getAndIncrement时做了什么呢?接下来分析下源码。

源码:

底层操作了一个UnSafe类,这个类是什么?
unsafe类代码

UnSafe

是CAS的核心类 由于Java 方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UNSafe类的方法.UnSafe类在于sun.misc包中, 是CAS的核心类 由于Java方法无法直接访问底层 ,需要通过本地(native)方法来访问,UnSafe相当于一个后面,基于该类可以直接操作特额定的内存数据。 注意UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务。

变量ValueOffset

便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的偏移地址 2.变量ValueOffset,便是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的。

看了以上代码和原理之后,原子引用类之所以能保证原子性原因在于使用了CAS机制,可以理解为check and set 。先跟原来的内容值比较,如果相等就赋新值,如果不等那么重新循环判断。接下来看看getAndAddInt源码,核心思想其实在于 dowhile语句。

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); //这里是来根据对象地址和偏移量来获取对应的主内存中的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//根据获取主内存值再与刚才获取的var5进行比较,如果相同则进行var5+var4,否则重新进行循环
        return var5;
    }
-----------------------------------------------------------
 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); //底层调用native,涉及汇编
 ---------------------------------------------------------

getAndAddInt也就是运用了CAS思想,这里的var1为对象,var2为变量在内存中的偏移地址,根据var1 和var2 就可以拿到这个对象中的值。var5对类中变量操作之前的值,var5 + var4为要更新的值。

过程如下:首先根据var1 和var2 获取到最新的变量值,然后对其值进行更新,但是要保证在操作的时候没有别的线程对这个值进行了修改,这里就要使用compareAndSwapInt()去判断一下,var5期望之前的变量值,如果这个值没有被别的对象修改过,那么就var5 + var4更新var5的值。
一句话说明:就是先比较是不是当前线程之前读的值,如果是就赋值,不是就去do while循环拿到最新值,然赋值。
compareAndSwapInt汇编底层
小总结:
简单版小总结
CAS缺点:
1、循环时间长开销很大(多个线程操作共享变量时,要去比较然后赋值,如果中途被别的线程改了,又要进行循环判断)
2、只能保证一个共享变量的原子性
3、引出来ABA问题???

下节讲诉:面试衍生问题,什么时ABA问题?

发布了68 篇原创文章 · 获赞 9 · 访问量 7437

猜你喜欢

转载自blog.csdn.net/u013025649/article/details/104239170
今日推荐