问题

(1)什么是原子操作?

(2)原子操作和数据库的ACID有啥关系?

(3)AtomicInteger是怎么实现原子操作的?

(4)AtomicInteger是有什么缺点?

简介

AtomicInteger是java并发包下面提供的原子类,主要操作的是int类型的整型,通过调用底层Unsafe的CAS等方法实现原子操作。

还记得Unsafe吗?点击链接直达【死磕 java魔法类之Unsafe解析

原子操作

原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。

原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分,将整个操作视作一个整体是原子性的核心特征。

我们这里说的原子操作与数据库ACID中的原子性,笔者认为最大区别在于,数据库中的原子性主要运用在事务中,一个事务之内的所有更新操作要么都成功,要么都失败,事务是有回滚机制的,而我们这里说的原子操作是没有回滚的,这是最大的区别。

源码分析

主要属性

// 获取Unsafe的实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 标识value字段的偏移量
private static final long valueOffset;
// 静态代码块,通过unsafe获取value的偏移量
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 存储int类型值的地方,使用volatile修饰
private volatile int value;

(1)使用int类型的value存储值,且使用volatile修饰,volatile主要是保证可见性,即一个线程修改对另一个线程立即可见,主要的实现原理是内存屏障,这里不展开来讲,有兴趣的可以自行查阅相关资料。

(2)调用Unsafe的objectFieldOffset()方法获取value字段在类中的偏移量,用于后面CAS操作时使用。

compareAndSet()方法

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// Unsafe中的方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

调用Unsafe.compareAndSwapInt()方法实现,这个方法有四个参数:

(1)操作的对象;

(2)对象中字段的偏移量;

(3)原来的值,即期望的值;

(4)要修改的值;

可以看到,这是一个native方法,底层是使用C/C++写的,主要是调用CPU的CAS指令来实现,它能够保证只有当对应偏移量处的字段值是期望值时才更新,即类似下面这样的两步操作:

if(value == expect) {
value = newValue;
}

通过CPU的CAS指令可以保证这两步操作是一个整体,也就不会出现多线程环境中可能比较的时候value值是a,而到真正赋值的时候value值可能已经变成b了的问题。

getAndIncrement()方法

public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
} // Unsafe中的方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

getAndIncrement()方法底层是调用的Unsafe的getAndAddInt()方法,这个方法有三个参数:

(1)操作的对象;

(2)对象中字段的偏移量;

(3)要增加的值;

查看Unsafe的getAndAddInt()方法的源码,可以看到它是先获取当前的值,然后再调用compareAndSwapInt()尝试更新对应偏移量处的值,如果成功了就跳出循环,如果不成功就再重新尝试,直到成功为止,这可不就是(CAS+自旋)的乐观锁机制么^^

AtomicInteger中的其它方法几乎都是类似的,最终会调用到Unsafe的compareAndSwapInt()来保证对value值更新的原子性。

总结

(1)AtomicInteger中维护了一个使用volatile修饰的变量value,保证可见性;

(2)AtomicInteger中的主要方法最终几乎都会调用到Unsafe的compareAndSwapInt()方法保证对变量修改的原子性。

彩蛋

(1)为什么需要AtomicInteger?

让我们来看一个例子:

public class AtomicIntegerTest {
private static int count = 0; public static void increment() {
count++;
} public static void main(String[] args) {
IntStream.range(0, 100)
.forEach(i->
new Thread(()->IntStream.range(0, 1000)
.forEach(j->increment())).start()); // 这里使用2或者1看自己的机器
// 我这里是用run跑大于2才会退出循环
// 但是用debug跑大于1就会退出循环了
while (Thread.activeCount() > 1) {
// 让出CPU
Thread.yield();
} System.out.println(count);
}
}

这里起了100个线程,每个线程对count自增1000次,你会发现每次运行的结果都不一样,但它们有个共同点就是都不到100000次,所以直接使用int是有问题的。

那么,使用volatile能解决这个问题吗?

private static volatile int count = 0;

public static void increment() {
count++;
}

答案是很遗憾的,volatile无法解决这个问题,因为volatile仅有两个作用:

(1)保证可见性,即一个线程对变量的修改另一个线程立即可见;

(2)禁止指令重排序;

这里有个很重要的问题,count++实际上是两步操作,第一步是获取count的值,第二步是对它的值加1。

使用volatile是无法保证这两步不被其它线程调度打断的,所以无法保证原子性。

这就引出了我们今天讲的AtomicInteger,它的自增调用的是Unsafe的CAS并使用自旋保证一定会成功,它可以保证两步操作的原子性。

public class AtomicIntegerTest {
private static AtomicInteger count = new AtomicInteger(0); public static void increment() {
count.incrementAndGet();
} public static void main(String[] args) {
IntStream.range(0, 100)
.forEach(i->
new Thread(()->IntStream.range(0, 1000)
.forEach(j->increment())).start()); // 这里使用2或者1看自己的机器
// 我这里是用run跑大于2才会退出循环
// 但是用debug跑大于1就会退出循环了
while (Thread.activeCount() > 1) {
// 让出CPU
Thread.yield();
} System.out.println(count);
}
}

这里总是会打印出100000。

(2)说了那么多,你知道AtomicInteger有什么缺点吗?

当然就是著名的ABA问题啦,我们下章接着聊^^


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java并发包之AtomicInteger源码分析的更多相关文章

  1. 死磕 java并发包之LongAdder源码分析

    问题 (1)java8中为什么要新增LongAdder? (2)LongAdder的实现方式? (3)LongAdder与AtomicLong的对比? 简介 LongAdder是java8中新增的原子 ...

  2. 死磕 java并发包之AtomicStampedReference源码分析(ABA问题详解)

    问题 (1)什么是ABA? (2)ABA的危害? (3)ABA的解决方法? (4)AtomicStampedReference是什么? (5)AtomicStampedReference是怎么解决AB ...

  3. 死磕 java同步系列之Semaphore源码解析

    问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaph ...

  4. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  5. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  6. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  7. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

  8. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  9. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

随机推荐

  1. java map 装入list

    需要生成多组数据的时候,应将map时候放入循环,否则循环出来会一直覆盖之前的,只能保存一条数据. 具体如下: if (rs.next()) { do { Map<String, String&g ...

  2. .NET 4.0 System.Threading.Tasks学习笔记

    由于工作上的需要,学习使用了System.Threading.Tasks的使用,特此笔记下来. System.Threading.Tasks的作用: Tasks命名空间下的类试图使用任务的概念来解决线 ...

  3. Nth prime & numbers of primes (模板)

    都是取的模板,这几天做的素数题挺多的,所以整理了放在这里,感觉有一天回用到的! SPOJ:Nth Prime:     求第N个素数,N<1e9. #include<bits/stdc++ ...

  4. 移动端网页巧用 margin和padding 的百分比实现自适应

    一个基础却又容易混淆的css知识点 本文依赖于一个基础却又容易混淆的css知识点:当margin/padding取形式为百分比的值时,无论是left/right,还是top/bottom,都是以父元素 ...

  5. RESTEasy使用json返回的例子

    创建一个json的model类: package com.howtodoinjava.model; import java.io.Serializable; import javax.xml.bind ...

  6. E - Alice and Bob

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  7. In-App Purchase Programming Guide----(五) ----Delivering Products

    Delivering Products In the final part of the purchase process, your app waits for the App Store to p ...

  8. Ribbon整合Eureka,出现 No instances available for XXX 异常

    请观察这里的片段有没有问题? @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ...

  9. c++常见面试题30道

    1.new.delete.malloc.free关系 delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数.malloc与free是C++/C语言的标准库函数,new ...

  10. HDU5213(容斥定理+莫队算法)

    传送门 题意 给出n个数和幸运数k,m次询问,每次询问[l1,r1]和[l2,r2]有多少对数满足x+y=k,x∈[l1,r1],y∈[l2,r2] 分析 看到m只有3e4,可以考虑\(m\sqrt{ ...