Java多线程:AtomicInteger 原子更新基本类型类 Java多线程系列--“JUC原子类”02之 AtomicLong原子类

前言

原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以它不是一个原子操作(线程执行a=0这个语句时直接将数据写入内存中;而执行a++时,会先获取a的值,再去执行加操作,最后再将数据写入内存)。只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

Atomic包介绍

在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

原子更新基本类型

用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

AtomicInteger的常用方法如下:

  • AtomicInteger(int initialValue):创建一个AtomicInteger实例,初始值由参数指定。不带参的构造方法初始值为0。
  • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果,与getAndAdd(int delta)相区分,从字面意思即可区分,前者返回相加后结果,后者先返回再相加。
  • boolean compareAndSet(int expect, int update) :如果当前值等于预期值,则以原子方式将该值设置为输入的值。
  • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
  • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。关于该方法的更多信息可以参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工作的?
  • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

AtomicInteger例子代码如下:

 1 import java.util.concurrent.atomic.AtomicInteger;
 2 
 3 public class Demo
 4 {
 5     // 初始值设为1
 6     static AtomicInteger atom = new AtomicInteger(1);
 7 
 8     public static void main(String[] args)
 9     {
10         System.out.println("初始值 = " + atom);
11         
12         // 以原子的方式加1,注意是先返回原数值再加1
13         System.out.println("调用getAndIncrement()返回值 = " + atom.getAndIncrement());
14         System.out.println("调用getAndIncrement()后初始值变为 = " + atom);
15 
16         // 以原子的方式与指定数值相加,注意是先加再返回相加后的值
17         System.out.println("调用addAndGet()返回值 = " + atom.addAndGet(10));
18         System.out.println("调用addAndGet()后初始值变为 = " + atom);
19 
20         // 以原子的方式将当前值设为指定数值,注意是先返回原值再设为指定值
21         System.out.println("调用getAndSet()返回值 = " + atom.getAndSet(-5));
22         System.out.println("调用getAndSet()后初始值变为 = " + atom);
23 
24         // 如果当前值等于预期值(第一个参数),则以原子的方式将当前值设置为指定值,并返回true,否则返回false
25         System.out.println("调用compareAndSet()返回值 = " + atom.compareAndSet(-5, 100));
26         System.out.println("调用compareAndSet()后初始值变为 = " + atom);
27     }
28 }

结果如下:

初始值 = 1
调用getAndIncrement()返回值 = 1
调用getAndIncrement()后初始值变为 = 2
调用addAndGet()返回值 = 12
调用addAndGet()后初始值变为 = 12
调用getAndSet()返回值 = 12
调用getAndSet()后初始值变为 = -5
调用compareAndSet()返回值 = true
调用compareAndSet()后初始值变为 = 100

那 AtomicInteger 怎么实现院子操作的呢,以 getAndIncrement() 为例,我们看看源码是怎样实现这个方法的

public final int getAndIncrement() {
    for (;;) {
        // 获取AtomicInteger当前对应的long值
        int current = get();
        // 将current加1
        int next = current + 1;
        // 通过CAS函数,更新current的值
        if (compareAndSet(current, next))
            return current;
    }
}

getAndIncrement()首先会根据get()获取AtomicInteger对应的int值。该值是volatile类型的变量,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时发现此时值改变了,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

get()的源码如下:

// value是AtomicInteger对应的int值
private volatile int value;
// 返回AtomicInteger对应的int值
public final int get() {
    return value;
}

getAndIncrement()接着将current加1,然后通过CAS函数(乐观锁),将新的值赋值给value。compareAndSet()的源码如下:

1 public final boolean compareAndSet(intexpect, int update) {
2     return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
3 }

调用 Unsafe 来实现

private static final Unsafe unsafe = Unsafe.getUnsafe();

这段代码写得很巧妙:

1. compareAndSet 方法首先判断当前内存值this是否等于预期值current;

2. 如果当前值 = current,说明 AtomicInteger 类的值没有被其他线程修改,则将内存值更新为next

3. 如果当前值 != current,说明 AtomicInteger 类的值已经被其他类修改了,这时会再次进入循环重新获取更新后值并比较

valueOffset表示的是变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的原值的。

Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,它提供了硬件级别的原子操作。让我们一起看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。

参考资料:

Java多线程系列--“JUC原子类”02之 AtomicLong原子类

Java中的Atomic包使用指南

java中的原子操作类AtomicInteger及其实现原理