java 原子类_没用过Java原子类?我来手写一个AtomicInteger

问题引出

大家可能听过「Automic」原子类,这些类在多线程下可以保证线程安全。比如想要实现自增,多线程下会出现少增加的情况。

public class VolatileAtomicTest {    public static int num = 0;    public  static void increase() {        num++;    }      public static void main(String[] args) throws InterruptedException {        Thread[] threads = new Thread[10];        for (int i = 0; i  {                for (int j = 0; j 

上面代码创建了10个线程,这10个线程要依次循环1000次,每次增加1,最后要求。

但实际情况是

3cef2826c340c570e7694832f322c47d.png

多线程情况下会出现第一个线程还在运算,第二个线程就运算完并覆盖了第一个线程的值运算结果,所以会出现与预期不符的结果。原因在

public  static void increase() {        num++;} 

num++我们可以拆解为

int a = num + 1;   //步骤1num = a;           //步骤2

如果线程一只执行到步骤1,还没执行到步骤2,线程二这时执行了步骤2。那么num的值就是线程2计算的值,而线程一的值就覆盖了。

如果我们保证这两步的原子性(操作一体,不能被其他线程插入)就可以得到预期结果。我们在这个方法下面加锁即可。

public synchronized static void increase() {    num++;}

或者

static Lock lock = new ReentrantLock();public static void increase() {    try {        lock.lock();        num++;    } finally {        lock.unlock();    }} 
c30fc3bf5f5e50bba400b750a65d3f7b.png

但是,上面的方法都使用了锁,在多线程下对性能还是有影响的。我们可以使用无锁化的原子类,实现原子自增。

@Testpublic void test() throws InterruptedException {    Thread[] threads = new Thread[10];    for (int i = 0; i  {            for (int j = 0; j 
5f5a63d184d14b6fb6f4051112d6466e.png

原子类

41c2dab929a79f5d619f91d507855d67.png

上图就是Java原子类的全家桶,主要是通过CAS + 自旋实现的。这里我们主要说说AtomicInteger,来看看incrementAndGet()方法。Java8增加了一些类,优化自选带来的性能问题。

/**     * Atomically increments by one the current value.     *     * @return the previous value     */    public final int getAndIncrement() {        return unsafe.getAndAddInt(this, valueOffset, 1);    }
/ setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();

发现用了Unsafe的getAndAddInt方法。至于Unsafe,底层也是操作系统的类,可以直接修改操作系统内存,或者调度线程。看着名字就知道不建议程序员使用它。

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));    return var5;}
  • var1: Object
  • var2: valueOffset
  • var5: 当前该变量在内存中的值
  • var5 + var4: 需要写进去的值

这里就是通过CAS修改值,不断循环,知道修改成功。但在高并发情况下,存在一些问题:

高并发量的情况下,由于真正更新成功的线程占少数,容易导致循环次数过多,浪费时间,并且浪费线程资源。

由于需要保证变量真正的共享,「缓存一致性」开销变大。

getIntVolatile()方法是系统的本地方法

public native int getIntVolatile(Object var1, long var2);

compareAndSwapInt()也是本地方法,这里就是常说的CAS了。

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

手写AtomicInteger

「首先定义几个变量」

private volatile int value;private static long offset;//偏移地址private static Unsafe unsafe;

我们定义了value值,用来保存当前的值。offset偏移地址,在类初始化的时候,计算出value变量在对象中的偏移。Unsafe类,直接操作系统内存。

「初始化变量」

// 通过Unsafe计算出value变量在对象中的偏移static {    try {        Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");        theUnsafeField.setAccessible(true);        unsafe = (Unsafe) theUnsafeField.get(null);        Field field = MyAtomicInteger.class.getDeclaredField("value");        offset = unsafe.objectFieldOffset(field);//获得偏移地址    } catch (Exception e) {        e.printStackTrace();    }}

我们反射获取Unsafe的theUnsafe字段,自定义的value字段,还有偏移地址offset。

「自增方法」

public void increment(int num) {    int tempValue;    do {        tempValue = unsafe.getIntVolatile(this, offset);//拿到值    } while (!unsafe.compareAndSwapInt(this, offset, tempValue, value + num));//CAS自旋}

「测试结果」

public class MyAtomTest {    public static void main(String[] args) {        Thread[] threads = new Thread[10];        MyAtomicInteger atomicInteger = new MyAtomicInteger();        for (int i = 0; i  {                for (int j = 0; j 
dc59e795f7f1d87ca9d08e096d6f7c8f.png

「Atomic」原子类解决了高并发下线程安全问题,但是高并发下也带来了性能问题,如果你的项目需要使用原子类,并且性能要求高,可以使用「Java8」中的原子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值