java并发编程直是学习和Java面试的重点,这篇主要是讲解AtomicInteger @mikechen
AtomicInteger简介
AtomicInteger 是一个支持原子操作的 Integer 类,它提供了原子自增方法、原子自减方法以及原子赋值方法等。
为什么需要AtomicInteger
谈到线程安全,会首先想到了synchronized 和 Lock,但是这种方式又有一个名字,叫做互斥锁,一次只能有一个持有锁的线程进入,再加上还有不同线程争夺锁这个机制,效率比较低,所以又称 悲观锁。
与之相对应,就有了 乐观锁 的概念,它不加锁去完成某项操作,如果因为冲突失败就重试,直到成功为止。
AtomicInteger 保证线程安全就是使用了乐观锁,所以相对于悲观锁,效率更高。
AtomaticInteger的应用场景
AtomaticInteger最典型的应用场景是计数,比如我们要统计并发插入10万条数据的耗时,我们需要对插入的数据计数。
普通的int变量在多线程环境下的++操作,是线程不安全的,前一个操作可能会被后一个操作所覆盖,所以统计的技术往往小于准确值,这时候就可以使用AtomaticInteger,使用非常简单:
private AtomicInteger counter = new AtomicInteger(0);//初始计数为0 // doSomething,执行操作之后,计数即可 int count = counter.incrementAndGet();
AtomicInteger常见用法
要将它用作计数器,AtomicIntegerclass提供了一些以原子方式执行加法和减法操作的方法。
addAndGet()- 以原子方式将给定值添加到当前值,并在添加后返回新值。 getAndAdd() - 以原子方式将给定值添加到当前值并返回旧值。 incrementAndGet()- 以原子方式将当前值递增1并在递增后返回新值。它相当于i ++操作。 getAndIncrement() - 以原子方式递增当前值并返回旧值。它相当于++ i操作。 decrementAndGet()- 原子地将当前值减1并在减量后返回新值。它等同于i-操作。 getAndDecrement() - 以原子方式递减当前值并返回旧值。它相当于-i操作。
下面具体的使用方式:
public class Main{ public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(100); System.out.println(atomicInteger.addAndGet(2)); //102 System.out.println(atomicInteger); //102 System.out.println(atomicInteger.getAndAdd(2)); //102 System.out.println(atomicInteger); //104 System.out.println(atomicInteger.incrementAndGet()); //105 System.out.println(atomicInteger); //105 System.out.println(atomicInteger.getAndIncrement()); //105 System.out.println(atomicInteger); //106 System.out.println(atomicInteger.decrementAndGet()); //105 System.out.println(atomicInteger); //105 System.out.println(atomicInteger.getAndDecrement()); //105 System.out.println(atomicInteger); //104 } }
AtomicInteger的实现原理
AtomicInteger 是一个支持原子操作的 Integer 类,就是保证对AtomicInteger类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。
接下来通过源代码来看AtomicInteger具体是如何实现的原子操作,首先看AtomicInteger 的自增方法,incrementAndGet() 方法,代码如下:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
通过源码,可以知道,这个方法的做法为先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面。
CAS(Compare And Swap)即比较并交换,CAS 是乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
它包含三个参数:V 内存值,预期值 A,要修改的新值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做,原理图如下所示:
这样使用CAS的方法就保证了原子操作,除此之外还有一个volatile修饰的value值也是关键。
这里我们结合AtomicInteger类的来说,源码如下:
public class AtomicInteger extends Number implements java.io.Serializable { // 省略部分代码 // 使用volatile修饰了一个变量用来存储数值 private volatile int value; // 构造函数直接初始化value值 public AtomicInteger(int initialValue) { value = initialValue; } // 无参构造,此时value默认为0 public AtomicInteger() { }
其中 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
private volatile int value;
volatile 是一种稍弱的同步机制,用来确保将变量的更新操作通知到其他线程。
当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制。
一句话总结ActomicInteger的实现原理:主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
AtomicInteger总结
使用 AtomicInteger替换普通 int 类型执行自增的原子操作,能够保证了线程安全,AtomicInteger由硬件提供原子操作指令实现的,在非激烈竞争的情况下,开销更小,速度更快。。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》