AtomicInteger源码分析

本文深入探讨了乐观锁和悲观锁的概念,分析了两者在多线程环境下的优缺点,重点介绍了CAS(Compare and Swap)作为乐观锁的一种实现方式,以及AtomicInteger如何利用CAS保证原子操作,避免多线程下的数据不一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考文章

java Unsafe类中compareAndSwap相关介绍

AtomicInteger源码分析——基于CAS的乐观锁实现

Java多线程之AtomicReference,CAS

AtomicInteger源码分析

1.乐观锁和悲观锁

  首先我们要明白一个知识点,乐观锁和悲观锁到底是什么

1.1 悲观锁

1.2.1 概念

  cpu是时分复用的,也就是把cpu的时间片,分配给不同的thread/process轮流执行,时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空寄存器,缓存数据。然后重新加载新的thread所需数据。当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

1.2.2 悲观锁的缺点

  在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,它不能做任何事,所以悲观锁有很大的缺点。

例子:如果一个线程需要某个资源,但是这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起这个线程,可能立刻就发现资源可用,然后又需要花费很 的长的时间重新抢占锁,时间代价就会非常的高。

1.2 乐观锁

  每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。比如CAS就是一种乐观锁思想的应用。

2.CAS(Compare and Swap)

2.1 概念

  CAS就是Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

A=V
A!=V
预期值A
判断A与V是都相等
V修改成B
什么也不做

2.2 JDK中的CAS(JUC)

是通过compareAndSwapXXX方法实现的

/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
* 
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

  JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令。在java.util.concurrent.atomic包下面的所有的原子变量类型中,比如AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作。

2.2.1CAS操作中的ABA问题

如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤

解决方案:
不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。

A变成B
B变成A
A1
B2
A3

AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,Atomic   MarkableReference将更新一个“对象引用-布尔值”的二元组。

2.3 AtomicInteger的实现

  AtomicInteger 是一个支持原子操作的 Integer 类,就是保证对AtomicInteger类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。如果不使用AtomicInteger,要实现一个按顺序获取的ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的 ID 的现象

接下来通过源代码来看AtomicInteger具体是如何实现的原子操作
首先看incrementAndGet() 方法,下面是具体的代码。

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

getAndAddInt()方法调用的compareAndSwapInt()方法的声明如下,是一个native方法。

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            /**获取原始值*/
            var5 = this.getIntVolatile(var1, var2);
        /**确认原始值没有被其它线程修改时,再执行更新var5+var4操作*/
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
        return var5;
    }

getAndAddInt()有3个参数,var1为当前传入的对象(count)如果要执行2+1=3这个操作,即var为2(内存值),var4为1,var5(主内存值,如果没有其他线程处理过这个主内存值,那么它应该和va2相等,如果不相等,应该重新从内存中取var2的值)。先判断预存值和内存值是否相等,如果相等,就相加
image
var1为当前线程,var2为本地内存A的缓存的值,var5为主内存的当前值,如果原始值没有被其他内存修改,才执行var5+var4,如果var2的值和var5的值不相同了,就重新才主内存中或者var2的值。

static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

  本段代码中的var2存的是对象的内存偏移量,对象调用了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,以获得他的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值