深入解析Java AtomicInteger原子类型
在并发编程中,需要确保当多个线程同时访问时,程序能够获得正确的结果,即实现线程安全。线程安全性定义如下:
当多个线程访问一个类时,无论如何调度运行时环境或如何交替执行这些线程,并且主代码中不需要额外的同步或协作,该类都可以正确地运行,因此该类是线程安全的。
线程安全需要两件事:
- 保证线程的内存可见性
- 保证原子性
以线程不安全性为例。如果我们想要实现一个函数来对页面访问进行计数,那么您可能想要count+,但是这个增量操作不是线程安全的。Count++可以分为三个操作:
- 获取变量当前值
- 给获取的当前变量值+1
- 写回新的值到变量
假设计数的初始值为10,当执行并发操作时,线程A和B可以同时进行1次操作,然后进行2次操作。A前进到3+1,当前值为11。注意,AB刚才获得的当前值是10,所以在B执行3次操作之后,计数仍然是11。这个结果显然不符合我们的要求。
因此,我们需要使用本文的主角Atomic Integer来确保线程安全。
Atomic Integer的源代码如下:
1 package java.util.concurrent.atomic; 2 import sun.misc.Unsafe; 3 4 public class AtomicInteger extends Number implements java.io.Serializable { 5 private static final long serialVersionUID = 6214790243416807050L; 6 7 // setup to use Unsafe.compareAndSwapInt for updates 8 private static final Unsafe unsafe = Unsafe.getUnsafe(); 9 private static final long valueOffset; 10 11 static { 12 try { 13 valueOffset = unsafe.objectFieldOffset 14 (AtomicInteger.class.getDeclaredField("value")); 15 } catch (Exception ex) { throw new Error(ex); } 16 } 17 18 private volatile int value; 19 20 public AtomicInteger(int initialValue) { 21 value = initialValue; 22 } 23 24 public AtomicInteger() { 25 } 26 27 public final int get() { 28 return value; 29 } 30 31 public final void set(int newValue) { 32 value = newValue; 33 } 34 35 public final void lazySet(int newValue) { 36 unsafe.putOrderedInt(this, valueOffset, newValue); 37 } 38 39 public final int getAndSet(int newValue) { 40 for (;;) { 41 int current = get(); 42 if (compareAndSet(current, newValue)) 43 return current; 44 } 45 } 46 47 public final boolean compareAndSet(int expect, int update) { 48 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 49 } 50 51 public final boolean weakCompareAndSet(int expect, int update) { 52 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 53 } 54 55 public final int getAndIncrement() { 56 for (;;) { 57 int current = get(); 58 int next = current + 1; 59 if (compareAndSet(current, next)) 60 return current; 61 } 62 } 63 64 public final int getAndDecrement() { 65 for (;;) { 66 int current = get(); 67 int next = current - 1; 68 if (compareAndSet(current, next)) 69 return current; 70 } 71 } 72 73 public final int getAndAdd(int delta) { 74 for (;;) { 75 int current = get(); 76 int next = current + delta; 77 if (compareAndSet(current, next)) 78 return current; 79 } 80 } 81 82 public final int incrementAndGet() { 83 for (;;) { 84 int current = get(); 85 int next = current + 1; 86 if (compareAndSet(current, next)) 87 return next; 88 } 89 } 90 91 public final int decrementAndGet() { 92 for (;;) { 93 int current = get(); 94 int next = current - 1; 95 if (compareAndSet(current, next)) 96 return next; 97 } 98 } 99 100 public final int addAndGet(int delta) { 101 for (;;) { 102 int current = get(); 103 int next = current + delta; 104 if (compareAndSet(current, next)) 105 return next; 106 } 107 } 108 109 public String toString() { 110 return Integer.toString(get()); 111 } 112 113 114 public int intValue() { 115 return get(); 116 } 117 118 public long longValue() { 119 return (long)get(); 120 } 121 122 public float floatValue() { 123 return (float)get(); 124 } 125 126 public double doubleValue() { 127 return (double)get(); 128 } 129 130 }
AtomicInteger 类中定义的属性
不安全是JDK中的一个工具类,它主要实现与平台相关的操作。以下引用自
太阳。杂项。不安全是JDK中使用的工具类。通过将Java意义上的一些“不安全”功能暴露给Java层代码,JDK可以更多地使用Java代码来实现与平台相关的一些功能,并且需要使用本机语言(如C或C++)来实现。这个类不应该在JDK核心类库之外使用。
不安全的实现与本文的目标几乎没有关系。您只需要知道这个代码是获取堆内存中的值偏移量。偏移量在原子整数中非常重要。原子操作依赖于它。
Value的定义和volatile
AtomicInteger本身是一个整数,因此最重要的属性是值。让我们看看它是如何声明值的。
1 private volatile int value;
我们看到值使用易失性修饰符,那么什么是易失性呢?
易失性相当于同步的弱实现,也就是说,易失性实现了类似于同步的语义而无锁定机制。它确保以可预测的方式将易失性字段的更新传递给其他线程。
Volatile包含以下语义:
- Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
- volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。
volatile 主要特性有两点:1.防止重排序。2. 实现内存可见性 。内存可见性的作用是当一个线程修改了共享变量时,另一个线程可以读取到这个修改后的值。在分析AtomicInteger 源码时,我们了解到这里就足够了。
用CAS操作实现原子性
Atomic Integer中有很多方法,比如等价于i++的.mentAndGet()和等价于i+=n的getAndAdd()。
源码如下:
1 public final int incrementAndGet() { 2 for (;;) { 3 int current = get(); 4 int next = current + 1; 5 if (compareAndSet(current, next)) 6 return next; 7 } 8 } 9 10 public final boolean compareAndSet(int expect, int update) { 11 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 12 }
incrementAndGet()
方法实现了自增的操作。核心实现是先获取当前值和目标值(也就是value+1),如果compareAndSet(current, next)
返回成功则该方法返回目标值。那么compareAndSet是做什么的呢?理解这个方法我们需要引入CAS操作。
我们在大学操作系统课程中了解了独占锁和乐观锁的概念。独占锁意味着所有线程都需要挂起直到持有独占锁的线程释放锁;乐观锁假定没有冲突可以直接操作,如果由于冲突而失败,则重试直到操作成功。其中,CAS是用于乐观锁定的机制,称为比较和交换。
Atomic Integer中的CAS操作是.eAndSet(),其功能是每次根据值Offset从内存中检索数据,并将检索到的值与expect进行比较。如果数据是一致的,则将内存中的值更改为更新。如果数据不一致,则意味着内存中的数据已经更新,然后将回滚(期望值无效)。
这样,使用CAS方法保证了原子操作。Atomic Long、Atomic Boolean和Java中的其他方法的基本原理和思想与原子整数的基本原理和思想基本相同。这篇文章不会解释太多。
你可能会问,同步关键字也可以实现并发操作啊,为什么不使用同步呢?
事实上,在我研究Atomic Integer源代码之前,我认为它是通过同步的原子操作在内部实现的。后来,搜索发现,原子操作的原始同步实现会影响性能,因为Java中的同步锁是排他锁,虽然可以实现原子操作,但是这种实现的并发性能很差。
总结
总之,Atomic Integer主要实现线程安全的整数操作,以防止并发情况下出现异常结果。其内部实现主要依赖于JDK中易失性和不安全的类操作存储器数据。易失性修饰符确保了内存中的其他线程能够看到值得更改的值,即实现了内存可见性。CAS操作确保了原子整数可以安全地修改值,实现原子性。volatile和CAS操作的组合实现了线程安全。