问题

(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. Struts%$#区别

    1.#符号的用途一般有三种.1)访问非根对象属性,例如示例中的#session.msg表达式,由于Struts 2中值栈被视为根对象,所以访问其他非根对象时,需要加#前缀.实际上,#相当于Action ...

  2. physical processor, core, logical processor

    Processor Groups https://docs.microsoft.com/en-us/windows/desktop/ProcThread/processor-groups The 64 ...

  3. springboot在idea实现热部署

    1.在pom.xml引入热部署devtools依赖 <dependency> <groupId>org.springframework.boot</groupId> ...

  4. Kafka使用经验小结

    本文尽量从一个使用者的角度去记录一些在实战当中使用Kfaka所需要关注的要点,这样可能会贴切更多的读者,本文并不会介绍太多的Kafka的一些架构层次设计的知识,因为网上已经有一大堆的重复搬运的资料任由 ...

  5. hadoop异常:Be Replicated to 0 nodes, instead of 1

    Hadoop 坑爹的Be Replicated to 0 nodes, instead of 1 异常 博客分类: Java 编程 HadoopITeyeJSP算法Apache  有段时间不写博客了, ...

  6. I.MX6 android 源码下载

    /************************************************************************* * I.MX6 android 源码下载 * 说明 ...

  7. MyBatis学习 之 三、SQL语句映射文件(2)增删改查、参数、缓存

    2.2 select 一个select 元素非常简单.例如: <!-- 查询学生,根据id --> <select id="getStudent" paramet ...

  8. [USACO2007 Demo] Cow Acrobats

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1629 [算法] 贪心 考虑两头相邻的牛 , 它们的高度值和力量值分别为ax , ay ...

  9. CodeBlocks+Qt(MinGW)配置

    1.安装CodeBlocks 官网:http://www.codeblocks.org/ 下载地址:http://www.codeblocks.org/downloads/26 有以下两种选择 cod ...

  10. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 10. 使用EF Core

    支持的数据库:可以查看官方网站 https://docs.microsoft.com/en-us/ef/core/providers/ 安装了VS2017后会安装了LocalDB,验证localDB ...