AtomicInteger源码分析

    xiaoxiao2021-08-20  126

    要解决什么问题

    最常需要解决的问题是i++ 这个语义在多线程中是不安全的。虽然从语法上看上去是一个操作,实际上分为了三步。取出i的值,i+1,将i+1的计算结果赋值给i。假设i的初始值为5,一种不安全的情况如下:

    -123456线程1i(5)i+1(6)i(6)线程2i(5)i+1(6)i(6)

    也就是说两个线程并发执行i++操作,所得的结果都是6 !而我们期望应该是一个是6,一个是7。如果这个用于数据库主键的生成,这将导致严重的主键冲突。

    如何解决

    AtomicInteger类

    // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); 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; public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }

    value:用volatile修饰使得变量对所有线程可见,代表实际值,AtomicInteger.get()就是返回这个的值。

    valueOffset:value的偏移地址

    incrementAndGet():这是也就是我们常用的保证原子操作递增方法,它实际上调用了unsafe.getAndAddInt()

    Unsafe类并不能直接查看到源码,只能通过反编译来看。

    Unsafe类

    public final native boolean compareAndSwapInt(Object object, long valueOffset, int expected, int update); public final int getAndAddInt(Object object, long valueOffset, int increament) { int expected; do { expected = this.getIntVolatile(object, valueOffset); } while(!this.compareAndSwapInt(object, valueOffset, expected, expected + increament)); return expected; }

    先看看compareAndSwapInt方法,方法用native修饰,表示是一个本地方法,调用的是CAS指令执行,这是整个过程是一个原子操作。功能是如果地址(由object和valueOffset共同确定)中的值与expected相同,则设置改地址的值为update,并返回true,否则不更新且返回false。

    再看看getAndAddInt(),在这个方法中调用了this.getIntVolatile(object, valueOffset),表示获取地址中的值,那么整个getAndAddInt()方法的语义是:

    1.获取到对象中存储value地址的值

    2.然后再回去比较是否相等,相等则更新,否则从1开始重试直到成功。

    为什么AtomicInteger操作并不会出现线程问题:当两个线程同时进入do-while循环中后,只有第一个线程能执行更新操作成功并退出循环,第二个线程试图执行赋值操作因为地址中的值不一致而不会更新,只能重新执行循环,重新执行循环获得到的是最新的value值,所以此处并不会出现线程问题。

    该类的中其它方法与本文类似,故不重复分析。

    注:本文源码为JDK1.8

    转载请注明原文地址: https://ju.6miu.com/read-676739.html

    最新回复(0)