问题

(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. @class && #import

    先前被问到@class和#import的区别,我很直白的说使用@class是对要引用的类进行一个声明,不让编译器报错,到后面要用的时候再引入相应的类,而#import则会引入类的所有实例变量和方法.接 ...

  2. G.易彰彪的一张表

    易彰彪最近有点奇怪,一向爱打游戏他最近居然盯着一张全是大小写字母的表在看,好像在找什么东西.他说,这是他女神给他的一张表,他需要回答女神的问题——在忽略大小写(即大写字母和小写字母视为同一字母)的情况 ...

  3. git format-patch 用法

    git format-patch HEAD^ # git format-patch -s 1bbe3c8c197a35f79bfddaba099270a2e54ea9c7 please replace ...

  4. [RK3288][Android6.0] 调试笔记 --- 通用GPIO驱动控制LED【转】

    本文转载自:http://m.blog.csdn.net/kris_fei/article/details/69553422 Platform: ROCKCHIPOS: Android 6.0Kern ...

  5. bzoj3957: [WF2011]To Add or to Multiply

    gay队牛逼! 我们可以强行拆一下柿子,最终得到的值会是m^k*x+m^k*u(k)*a+m^k-1*u(k-1)*a……m^0*u(0)*a 其中u表示后面有i个m的a有多少个 答案就是k+∑u 枚 ...

  6. 第六届蓝桥杯C++B组省赛

    1.奖券数目 2.星系炸弹 3.三羊献瑞 4.格子中输出 5.九数组分数 6.加法变乘法 7.牌型种数 8.移动距离 9.垒骰子 10.生命之树 1.奖券数目 奖券数目有些人很迷信数字,比如带“4”的 ...

  7. codeforces 696C C. PLEASE(概率+快速幂)

    题目链接: C. PLEASE time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...

  8. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 23. 继续讲Tag Helpers 和复习View Component

    当条件为true就渲染,否则就不渲染 ‘ 判断用户的登陆 更好的一点是做一个TagHelper.把这些明显的C#代码都去掉.最终都是用html和属性的形式来组成一个最终的代码 属性名称等于Confit ...

  9. python __builtins__ help类 (32)

    32.'help', 接收对象作为参数,更详细地返回该对象的所有属性和方法 class _Helper(builtins.object) | Define the builtin 'help'. | ...

  10. 黑客攻防技术宝典web实战篇:攻击访问控制习题

    猫宁!!! 参考链接:http://www.ituring.com.cn/book/885 随书答案. 1. 一个应用程序可能通过使用 HTTP Referer 消息头实施访问控制,但它的正常行为并没 ...