面试准备--原子类 AtomicInteger 详解

原子类也是 java 并发编程学习中不可或缺的一个部分了。今天我们来学习一下 JUC 并发包下原子类 AtomicInteger 。

下面我们看看该类的类图:
在这里插入图片描述
类图很简单,没有太多的继承实现关系,就先来看看 Number 类:

public abstract class Number implements java.io.Serializable {

    public abstract int intValue();

    public abstract long longValue();

    public abstract float floatValue();
    
    public abstract double doubleValue();

    /**
	 * 提供最基本的方法转型
     */
    public byte byteValue() {
        return (byte)intValue();
    }
	/**
	 * 提供最基本的方法转型
     */
    public short shortValue() {
        return (short)intValue();
    }
}

抽象类 Number 是所有基本类型的包装类的父类,提了了一些数据类型转型的方法。具体的以后写 Integer 之类源码分析讲解。

CAS简介:
可能很多同学都不知道 CAS 是什么,下面我们来进行简单讲解:

无锁操作常见的指令:
比较并交换(全称 Compare-and-Swap,简称 CAS)

(以下以 java 平台进行简述)

CAS 指令一般需要三个操作数,分别是变量的内存地址(用 V 表示)、旧的预期值(用 A 表示)、新值(用 B 表示)。CAS 指令执行时,只有当 V 的值符合 A 的时(如果是 int 类型,可以理解为 V == A 的情况下成立),处理器才会将新值 B 更新 V 的值,否则就是失败,不进行更新。但无论是否更新了 V 的值,都会返回 V 的旧的值。注:上述比较并交换的执行过程必须保证它 原子性(下面模拟实现使用了 synchronized 关键字保证)

不同版本代码比较:
记得以前版本的 JDK 中查看 AtomicInteger 的自增方法 incrementAndGet 的代码如下所示:

/**
* Atomically increment by one the current value
* @return the update value
*/
 public final int incrementAndGet() {
        for (;;) {
            int current= get();
           	int next = current + 1;
            if (compareAndSet(current, next)) {
                return next;
            }
        }
    }

后来,不再是我们代码中循环调用了,而是使用 unsafe 类中 native 方法来实现了,代码如下:

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

本质上没什么差别,都是进行自增操作,以前的是我们是自己实现一个死循环,不断尝试将当前值 +1 赋值给自己,失败后继续循环,直到赋值成功。

其实 CAS 指令在我们并发包下十分常见,很多并发容器都使用到 CAS,这里介绍一下 CAS 的一个缺陷 – ABA 问题

ABA 问题:
下面我们来看一下什么是 ABA 问题。这里先举个小例子:有三个线程 A,B,C,当前值为 1,线程 A 来时读到的值是 1,准备进行下一步 CAS 操作,这时线程 B 已经将原来的值改为了 2,线程 C 也在进行 CAS 将值又变回了 1,。线程 A 在接下来判断值是否为预期 1 ,是则修改成功。可是有一个问题,CAS 是不知道它曾经改变过的,这就可能会带来一些问题。

CAS 在大部分情况下是不影响程序并发的正确性,比如我们将数据自增到 100,是不影响的。假设是数据要强一致性的业务,那就要避免这样使用了,老老实实用 互斥锁 保证正确性吧。

ABA 问题可以使用 AtomicStampedReference/AtomicMarkableReference 类来解决,后续文章将会对这两个类进行分析,看看是如何解决 ABA 问题的。

附:
JDK1.8 中,对 AtomicInteger 原子类添加了几个新方法,方法如下:

	/**
 	 * 下面两个方法是自定义增加多少
 	 * IntUnaryOperator 是 1.8 新增接口,用于参数计算
 	 * 增加多是写死的
 	 */
	public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }

    public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }

 	/**
 	 * 下面两个方法是自定义增加多少
 	 * IntBinaryOperator 是 1.8 新增接口,用于参数计算
 	 * 与 IntBinaryOperator 区别是可以将传入数据进行相加
 	 * 也就是自定义增加或减少多少
 	 */
    public final int getAndAccumulate(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return prev;
    }

    public final int accumulateAndGet(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return next;
    }

最后:
我们知道,CAS 操作涉及到我们的操作系统(简单理解:平台相关),也就是我们点进代码上有个 native 关键字修饰的方法。下面,我们自己来实现一个简单的 CAS 方法。

/**
 * @author Gentle
 * @date 2019/04/20 : 15:52
 */
public class MyAtomicInteger {
	
    private volatile int count;

    /**
     * 自增
     * @return 自增成功后的值
     */
    public final int incrementAndGet() {
        int pre, next;
        for (; ; ) {
            pre = get();
            next = pre + 1;
            if (compareAndSet(pre, next)) {
                return next;
            }
        }
    }
    /**
     *  比较并交换,一定要保证原子性,所以加了 synchronized 锁
     * @param except 当前值
     * @param newValue 新值
     * @return 是否 CAS 成功
     */
    private boolean compareAndSet(int except, int newValue) {
        synchronized (this) {
            if (get() == except) {
                count = newValue;
                return true;
            }
        }
        System.out.println("CAS fail");
        return false;
    }
    /**
     * 获取值
     * @return 当前值
     */
    public int get() {
        return count;
    }
    
    public static void main(String[] args) throws InterruptedException {
        MyAtomicInteger myAtomicInteger = new MyAtomicInteger();
        //闭锁
        CountDownLatch countDownLatch = new CountDownLatch(10);
        /**
         * 模拟 10 线程 每条线程执行 5000 次
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5000; j++){
                    myAtomicInteger.incrementAndGet();
                }
                //完成后计数器减 1
                countDownLatch.countDown();

            }).start();
        }
        //等待所有线程完成工作
        countDownLatch.await();
        //拿出当前值
        System.out.println(myAtomicInteger.get());
    }
}

总结:
上面方法中可能会涉及 unsafe 类,该类不是提供给用户使用的类,只有类加载器加载 Class 的时候才能访问到它。实际上该类是一个后门,用于调用操作系统的方法。如果我们非要使用的话,可以尝试使用反射来调用。

CAS 中最具价值的就是 ABA 问题了,理解了 ABA 问题,大致这篇就可以过了。其他 JDK1.8 提供的新特性暂时可以不了解。

有兴趣的同学可以关注公众号
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值