原子操作AtomicInteger 及辅助类 Unsafe

本文深入剖析了Java中AtomicInteger类的工作原理,解释了其如何利用Unsafe类实现原子操作,确保多线程环境下整型变量的安全增减。通过具体方法分析,如getAndSet和compareAndSet,展示了AtomicInteger在并发编程中的应用。

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

我们知道,一般给int类型数据赋值,比如说 int i = 3; i++; 其中的 i++ 操作,不是原子级别的,它包含了三个步骤,先取值 i = 3; 然后加一,即 3+1,最后,把得到的值4赋值给i,因此,如果是多线程并发操作,就很容易造成问题了。java中为了解决这个问题,提供了 AtomicInteger 类,仔细看,发现属于 Atomic 系列。我打开了 AtomicInteger 的源码,发现它继承了 Number, 我们知道 Number 操作也不是原子级别的,但 AtomicInteger 是,那么就来看看看 AtomicInteger 中有什么特别的操作。

发现,里面最醒目的是 private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); 也就是 Unsafe 类,其它的 Number 方法基本没有重写,那么看来问题就在于Unsafe类了。android 中 对于这个类,没办法直接使用,它属于系统级别的调用,如果想使用的,反射应该是可以的,这里只是讲一下个人的体会,水平有限,请见谅。 根据上面的代码,可以确定 Unsafe 对外提供了静态方法,返回一个 Unsafe 对象。继续看,发现头部有几行代码

    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long VALUE;

    static {
        try {
            VALUE = U.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile int value;

我们知道,类被加载时,马上就会执行静态代码块,静态是随着类的加载而加载。我们注意看, AtomicInteger.class.getDeclaredField("value") 是使用了反射技术,获得 value 属性的 Field ,然后用 Unsafe 的 objectFieldOffset()方法,把它当做参数传了进去,得到一个 long 类型的 VALUE,这一静态代码块的意思,个人理解是根据反射获取field,然后通过Unsafe,根据 field 获取了属性 value 所在内存位置的指针地址值,类型是 long。

我们使用 AtomicInteger count = new AtomicInteger(0); 然后  int number = count.get(); 获取值,我们可以发现,AtomicInteger 是把数值记录在 value 属性中,而 VALUE 是value 在内存中的地址指针,那么可以大胆猜想是通过指针直接修改数据的值。好,继续看方法,

     public final int getAndSet(int newValue) {
        return U.getAndSetInt(this, VALUE, newValue);
    }

这个方法中, 看方法注释, Atomically sets to the given value and returns the old value. 意思是设置一个新值,返回原先的值。看看调用的 U.getAndSetInt(this, VALUE,newValue);方法,Unsafe 类,把当前对象类 AtomicInteger 和 value对应的内存中指针的值 VALUE 穿进去,同时把新值也传递进去,我们可以想象为 Unsafe 控制内存,根据前两个参数,找到了value值对应的地址值,然后把第三个参数赋值给指针对应的值,然后把原乡指针对应的值返回回来,这样就好理解了。

看第二个方法

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

有了上一个方法的经验,我们可以知道,比上面多了一个参数,按照方法名的意思,是比较数值并且设置数值,那么,同样道理,内存中找到value的指针,然后进行赋值替换,expect是原始值, update 是要更新的值,成功了就返回true,失败了就返回false。这里有个重点,比如说

    AtomicInteger count = new AtomicInteger();
    count.set(1);
此时,value的值为1,那么我们使用 compareAndSet()方法时,第一个参数就需要是1,这样才能替换成功,否则失败;

    private static void test() {
        AtomicInteger count = new AtomicInteger();
        count.set(1);
        boolean b = count.compareAndSet(1, 2);
        int num = count.get();
        System.out.println(" b= " + b + "  num= " + num);
    }
打印的结果是 b= true  num= 2。 如果修改数值,boolean b = count.compareAndSet(1, 2); 中的参数修改, boolean b = count.compareAndSet(3, 2);看看打印的值

    private static void test() {
        AtomicInteger count = new AtomicInteger();
        count.set(1);
        boolean b = count.compareAndSet(3, 2);
        int num = count.get();
        System.out.println(" b= " + b + "  num= " + num);
    }
打印的结果是 b= false  num= 1。

其实这两个方法就是核心,compareAndSet()方法,并发中CAS大概也是一样的道理吧。再看看其他方法,

    public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
    }

    public final int getAndDecrement() {
        return U.getAndAddInt(this, VALUE, -1);
    }

很明显了,每次都加上一个固定的值,getAndIncrement() 可以理解为对应的是 i++,getAndDecrement() 对应的是 i--,最终都是调用 U.getAndAddInt()方法,只是传的值一个是1,一个是-1;要注意的是, getAndIncrement() 返回的是 原始的value值,这个方法操作后 value 是被更新了, 就好比是 int i = 0; int a = i++; 返回的值是 a,然后再 i = a; 把a的值赋值给i。 代码验证一下

    private static void test7() {
        AtomicInteger count = new AtomicInteger();
        count.set(1);
        int b = count.getAndIncrement();
        int num = count.get();
        System.out.println(" b= " + b + "  num= " + num);
    }

打印结果是  b= 1  num= 2。

    public final int getAndAdd(int delta) {
        return U.getAndAddInt(this, VALUE, delta);
    }
这个可以理解为上面方法的封装,自己可以控制要增加和减小的值,增加就传正数,减小就传负数。


如果我们想是想 ++i 这种操作呢,AtomicInteger 提供了一个巧妙的方法,那就是把值再加一次返回

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

    public final int decrementAndGet() {
        return U.getAndAddInt(this, VALUE, -1) - 1;
    }

这就相当于 int i = 0; int a = i++ + 1; i = a; 这是一个技巧,同理

    public final int addAndGet(int delta) {
        return U.getAndAddInt(this, VALUE, delta) + delta;
    }
也就好理解了。

    public final void lazySet(int newValue) {
        U.putOrderedInt(this, VALUE, newValue);
    }
这个方法就是直接赋值的,相当于 int i = 3; i =5; 这个直接记住就行了。

对于 AtomicInteger 类分析了大部分源码,了解到如何使用,这时候再看 java.util.concurrent.atomic 包下面其它的类 AtomicLong AtomicBoolean  AtomicReference 等等,都是一个道理。

### 作用与使用方法 #### 概念与作用 原子操作如 `AtomicInteger` 是 Java 并发编程中用于保证共享变量操作原子性的工具。在多线程环境下,普通的变量操作(如自增、赋值)可能不是线程安全的,导致数据竞争问题。`AtomicInteger` 通过硬件级别原子操作指令(如 CAS,Compare-And-Swap)来确保操作原子性,从而避免了使用锁的性能开销。这使得它在高并发场景中具有更高的效率和更好的可扩展性[^2]。 #### 使用方法 `AtomicInteger` 提供了多种方法用于操作整型变量,这些方法包括但不限于: - `int get()`:获取当前值。 - `void set(int newValue)`:设置新值。 - `boolean compareAndSet(int expect, int update)`:比较并交换值,只有当前值等于 `expect` 时才更新为 `update`。 - `int getAndIncrement()`:获取当前值后自增。 - `int incrementAndGet()`:自增后获取新值。 - `int addAndGet(int delta)`:加上指定增量后获取新值。 以下是一个简单的示例,展示了 `AtomicInteger` 在并发环境中的使用: ```java import java.util.concurrent.atomic.AtomicInteger; public class Producer implements Runnable { private static AtomicInteger count = new AtomicInteger(); public void run() { String data = null; count.incrementAndGet(); data = "data:" + count.incrementAndGet(); System.out.println("将数据:" + data + "放入队列..."); } } ``` 在这个例子中,多个线程通过调用 `count.incrementAndGet()` 方法来确保对共享变量 `count` 的操作是线程安全的。这种方法避免了使用锁机制,同时保证了操作原子性[^5]。 #### 底层实现原理 `AtomicInteger` 的实现依赖于 `Unsafe` ,它提供了底层的硬件级原子操作。`Unsafe` 通过 CAS 操作(Compare-And-Swap)来实现变量的原子更新。例如,`AtomicInteger` 中的 `incrementAndGet()` 方法实际上调用了 `Unsafe` 的 CAS 操作来确保自增过程的原子性。以下是一个简化的 `AtomicInteger` 定义: ```java private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { } ``` 其中,`valueOffset` 是变量 `value` 在对象内存中的偏移地址,`Unsafe` 利用这个偏移地址来直接操作内存中的变量值,从而实现原子操作[^1]。 #### 函数式编程支持 `AtomicInteger` 还支持以函数式编程的方式更新值。例如,`accumulateAndGet` 方法允许传入一个二元操作函数,用于更新当前值。以下是一个示例: ```java import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { AtomicInteger atomicInt = new AtomicInteger(5); atomicInt.accumulateAndGet(3, (x, y) -> x * y); // 5 * 3 = 15 System.out.println(atomicInt.get()); // 输出 15 } } ``` 在这个示例中,`accumulateAndGet` 方法通过传入的函数 `(x, y) -> x * y` 更新当前值,这种设计使得 `AtomicInteger` 在某些场景下更加灵活[^4]。 --- ### 相关问题 1. `AtomicInteger` 在高并发环境下如何避免锁的性能开销? 2. 如何利用 `AtomicInteger` 实现线程安全的计数器? 3. `AtomicInteger` 的 `compareAndSet` 方法如何保证操作原子性? 4. `AtomicInteger` 与 `volatile` 变量在并发编程中的区别是什么? 5. 在哪些场景下更适合使用 `AtomicInteger` 而不是传统的锁机制?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值