AtomicInteger-用法
前言
AtomicInteger 源自 java.util.concurrent.atomic 包,另外有 AtomicBoolean、AtomicInteger、AtomicLong、AtomicLongArray、AtomicReference 等原子类,主要用于在高并发环境下,简化同步处理.
Atomic 包介绍
Java1.5 的 Atomic 包名为 java.util.concurrent.atomic。这个包提供了一系列原子类。这些类可以保证多线程环境下,当某个线程在执行 atomic 的方法时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由 JVM 从等待队列中选择一个线程执行。Atomic 类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的。
Atomic 包中的类可以分成 4 组:
AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
AtomicIntegerArray,AtomicLongArray
AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
我们来看一下最简单的 AtomicInteger 有哪些常见的方法以及这些方法的作用。
1 | get() 直接返回值 |
在 Java 中,++i 和 i++ 操作并不是线程安全的,在使用的时候,不可避免的会用到 synchronized 关键字。而 AtomicInteger 则通过一种线程安全的加减操作接口。
AtomicInteger 的基本方法
创建一个 AtomicInteger
1 | System.out.println(atomicInteger.get()); |
输出 : 123
创建一个不传值的,默认值为 0
1 | AtomicInteger atomicInteger = new AtomicInteger(); |
输出: 0
获取和赋值
1 | atomicInteger.get(); //获取当前值 |
输出结果为: 0 false 0
1 | public static void main(String[] args) { |
输出结果为: 123 true 234
由上可知该方法表示,atomicInteger 的值与 expectedValue 相比较,如果不相等,则返回 false,atomicInteger 原有值保持不变;如果两者相等,则返回 true,atomicInteger 的值更新为 newValue
getAndAdd()方法与 AddAndGet 方法
1 | AtomicInteger atomicInteger = new AtomicInteger(123); |
getAndDecrement()和 DecrementAndGet()方法
1 | AtomicInteger atomicInteger = new AtomicInteger(123); |
使用 AtomicInteger,即使不用同步块 synchronized,最后的结果也是 100,可用看出 AtomicInteger 的作用,用原子方式更新的 int 值。主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。
1 | public class Counter { |
运行结果: 100
使用普通 Integer
1 | public class Counter { |
运行结果:98
如果在 inc 方法前面加个 synchronized 也能是线程安全的;
它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
1 | import java.util.concurrent.CountDownLatch; |
运行结果:100
synchronized 的使用说明:
一、当两个并发线程访问同一个对象 object 中的这个 synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问 object 的一个 synchronized(this)同步代码块时,另一个线程仍然可以访问该 object 中的非 synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问 object 的一个 synchronized(this)同步代码块时,其他线程对 object 中所有其它 synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问 object 的一个 synchronized(this)同步代码块时,它就获得了这个 object 的对象锁。结果,其它线程对该 object 对象所有同步代码部分的访问都被暂时阻塞。
五、以上规则对其它对象锁同样适用.
6、从上面的例子中我们可以看出:使用 AtomicInteger 是非常的安全的.而且因为 AtomicInteger 由硬件提供原子操作指令实现的。在非激烈竞争的情况下,开销更小,速度更快。
java 的关键域有 3 个
1 | // setup to use Unsafe.compareAndSwapInt for updates |
这里, unsafe 是 java 提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是 AtomicInteger 中的一个工具。
valueOffset 是用来记录 value 本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到 value 的位置,方便比较。
注意:value 是用来存储整数的时间变量,这里被声明为 volatile,就是为了保证在更新操作时,当前线程可以拿到 value 最新的值(并发环境下,value 可能已经被其他线程更新了)。
这里,我们以自增的代码为例,可以看到这个并发控制的核心算法:
源码
1 | public final int updateAndGet(IntUnaryOperator updateFunction) { |
AtomicInteger 源码分析
1 | public final int incrementAndGet() { |
方法中采用了 CAS 操作,每次从内存中读取数据然后将此数据和+1 后的结果进行 CAS 操作,如果成功就返回结果,否则重试直到成功为止。而 compareAndSet 利用 JNI 来完成 CPU 指令的操作。
1 | public final boolean compareAndSet(int expect, int update) { |
整体的过程就是这样子的,利用 CPU 的 CAS 指令,同时借助 JNI 来完成 Java 的非阻塞算法。
什么是 CAS
CAS,Compare and Swap 即比较并交换。 java.util.concurrent 包借助 CAS 实现了区别于 synchronized 同步锁的一种乐观锁。乐观锁就是每次去取数据的时候都乐观的认为数据不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间数据有没有更新。CAS 有 3 个操作数:内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。CAS 的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
CAS 的优缺点
CAS 由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。
CAS 虽然很高效的实现了原子操作,但是它依然存在三个问题。
1、ABA 问题。CAS 在操作值的时候检查值是否已经变化,没有变化的情况下才会进行更新。但是如果一个值原来是 A,变成 B,又变成 A,那么 CAS 进行检查时会认为这个值没有变化,但是实际上却变化了。ABA 问题的解决方法是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就变成 1A-2B-3A。从 Java1.5 开始 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
2、并发越高,失败的次数会越多,CAS 如果长时间不成功,会极大的增加 CPU 的开销。因此 CAS 不适合竞争十分频繁的场景。
3、只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS 就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。比如有两个共享变量 i = 2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java1.5 开始 JDK 提供了 AtomicReference 类来保证引用对象的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。