[JDK并发包]AtomicInteger

前言:

AtomicInteger

美[əˈtɑːmɪk] 美[ˈɪntɪdʒər]

java并发机制中主要有三个特性需要我们去考虑,原子性、可见性和有序性。synchronized关键字可以保证可见性和有序性却无法保证原子性。而这个AtomicInteger的作用就是为了保证原子性。

为什么出现这个类?

详解java并发原子类AtomicInteger(基于jdk1.8源码分析)

因为在并发下面,打印i++的时候

对于i++的操作,其实可以分解为3个步骤。

(1)从主存中读取a的值

(2)对a进行加1操作

(3)把a重新刷新到主存

有的线程已经把a进行了加1操作,但是还没来得及重新刷入到主存,其他的线程就重新读取了旧值。因为才造成了错误。

那么这个数字直接用static AtomicInteger a = new AtomicInteger()

打印的时候可以使用a.incrementAndGet()

使用了AtomicInteger就不会出现这个问题。

1. 对AtomicInteger的方法分析:

[JDK并发包]AtomicInteger_第1张图片

里面有几个方法还需要在意一下:

int getAndIncrement 是先获取旧的值,然后进行自增

int incrementAndGet 是先增加,然后获取新的值

这两个方法有什么区别?

1.1 getAndIncrement() 

相当于先获取current,然后进行增加,然后进行compareAndSet(current, next) 循环去做 如果成功那么返回这个的current。

对于JDK7的话,《Java并发实现原理》中也是基于JDK7来讲:

 

    public final int getAndIncrement() {
        for (; ; ) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))//CAS
                return current;
        }
    }


public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

对于JDK8:

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    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;
    }

//如果把var填上值
    public final int getAndAddInt(Object obj, long valueOffset, int delta) {
        int expect;
        //自旋
        do {
            //获取主存的值
            expect = this.getIntVolatile(obj, valueOffset);
        //CAS操作
        } while(!this.compareAndSwapInt(obj, valueOffset, expect, expect + delta));
 
        //返回旧值
        return expect;
    }

封装了自旋锁 

直接封装unsafe方法中了,保证原子性,unsafe是JDK私有的我们不能调用

JDK7与8的区别主要在于JAVA8把循环都放到了unsafe类里面去。

 

 

1.2 incrementAndGet()

 

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

1.3  getAndIncrement与incrementAndGet两个方法的区别:

根据方法名字也能知道,第一个先返回当前值,然后+1;

后面的是+1然后返回新值。

    public static void main(String[] args) {
        AtomicInteger first = new AtomicInteger(5);
        int a = first.getAndIncrement();
        System.out.println("a value:"+a);
        
        AtomicInteger second = new AtomicInteger(5);
        int b = second.incrementAndGet();
        System.out.println("b value:"+b);
    }

输出:

a value:5
b value:6

2. 总结

当面试官问: 你读过源码么?

那么对于AtomicInteger就可以提到CAS和Unsafe类。

AtomicInteger的优点

1.乐观锁,性能较强,利用CPU自身的特性保证原子性,即CPU的指令集封装compare and swap两个操作为一个指令来保证原子性。

2.适合读多写少模式

但是缺点明显

1.自旋,消耗CPU性能,所以写的操作较多推荐sync

2.仅适合简单的运算,否则会产生ABA问题,自旋的时候,别的线程可能更改value,然后又改回来,此时需要加版本号解决,JDK提供了AtomicStampedReference和AtomicMarkableReference解决ABA问题,提供基本数据类型和引用数据类型版本号支持

2.1 附加:什么是ABA问题?

CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作,在某些算法中,如果V的值首先由A变为B,再由B变为A,那么CAS将会操作成功

2.2 java中CAS实现

JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平台的汇编指令

每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false

2.3 解决ABA的思路?

解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。

2.4 JAVA中ABA问题的解决方案?

AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。

//关键代码
public class AtomicStampedReference {
    private static class Pair {
        final T reference;  //维护对象引用
        final int stamp;  //用于标志版本
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static  Pair of(T reference, int stamp) {
            return new Pair(reference, stamp);
        }
    }
    private volatile Pair pair;
    ....
    /**
      * expectedReference :更新之前的原始值
      * newReference : 将要更新的新值
      * expectedStamp : 期待更新的标志版本
      * newStamp : 将要更新的标志版本
      */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair current = pair; //获取当前pair
        return
            expectedReference == current.reference && //原始值等于当前pair的值引用,说明值未变化
            expectedStamp == current.stamp && // 原始标记版本等于当前pair的标记版本,说明标记未变化
            ((newReference == current.reference &&
              newStamp == current.stamp) || // 将要更新的值和标记都没有变化
             casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair
    }
}

这个类怎么用:

private static AtomicStampedReference atomicStampedRef =
        new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
    Thread main = new Thread(() -> {
        System.out.println("操作线程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
        int stamp = atomicStampedRef.getStamp(); //获取当前标识别
        try {
            Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此时expectedReference未发生改变,但是stamp已经被修改了,所以CAS失败
        System.out.println("操作线程" + Thread.currentThread() +",CAS操作结果: " + isCASSuccess);
    },"主操作线程");
    Thread other = new Thread(() -> {
        Thread.yield(); // 确保thread-main 优先执行
atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操作线程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
        atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
        System.out.println("操作线程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
    },"干扰线程");
    main.start();
    other.start();
}

操作结果:

操作线程Thread[主操作线程,5,main],初始值 a = 2
操作线程Thread[干扰线程,5,main],【increment】 ,值 = 2
操作线程Thread[干扰线程,5,main],【decrement】 ,值 = 1
操作线程Thread[主操作线程,5,main],CAS操作结果: false

参考:ABA问题以及解决

 

参考:
https://blog.csdn.net/fenglllle/article/details/81316346

你可能感兴趣的:(jdk并发包)