AtomicInteger的原理

java的并发原子包里面提供了很多可以进行原子操作的类,比如:

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong
  • AtomicReference

等等,一共分为四类:原子更新基本类型(3个)、原子更新数组、原子更新引用和原子更新属性(字段)。、提供这些原子类的目的就是为了解决基本类型操作的非原子性导致在多线程并发情况下引发的问题。那么非原子性的操作会引发什么问题呢?下面我们通过一个示例来看一下。

1. i++引发的问题

我们知道基本类型的赋值操作是原子操作,但是类似这种i++的操作并不是原子操作,通过反编译代码我们可以大致了解此操作分为三个阶段:

tp1 = i;  //1
tp2 = tp1 + 1; //2
i = tp2; //3

如果有两个线程m和n要执行i++操作,因为重排序的影响,代码执行顺序可能会发生改变。如果代码的执行顺序是m1 - m2 - m3 - n1 - n2 - n3,那么结果是没问题的,如果代码的执行顺序是m1 - n1 - m2 - n2 - m3 - n3那么很明显结果就会出错。

测试代码

package com.wangjun.thread;

public class AtomicIntegerTest {

	private static int n = 0;

	public static void main(String[] args) throws InterruptedException {
//i++引发的线程问题
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
n++;
}
};
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
n++;
}
};
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终n的值为:" + n);
}
}

如果i++是原子操作,那么结果应该就是2000,反复运行几次发现结果大部分情况下都不是2000,这也证明了i++的非原子性在多线程下产生的问题。当然我们可以通过加锁的方式保证操作的原子性,但本文的重点是使用原子类的解决这个问题。

最终n的值为:1367
---
最终n的值为:1243
---
最终n的值为:1380

2. AtomicInteger的原子操作

上面的问题可以使用AtomicInteger来解决,我们更改一下代码如下:

package com.wangjun.thread;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

	private static AtomicInteger n2 = new AtomicInteger(0);

	public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 1000; i++) {
n2.incrementAndGet();
}
};
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i< 1000; i++) {
n2.incrementAndGet();
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终n2的值为:" + n2.toString());
}
}

多次运行,发现结果永远是2000,由此可以证明AtomicInteger的操作是原子性的。

最终n2的值为:2000

那么AtomicInteger是通过什么机制来保证原子性的呢?接下来,我们对源码进行一下分析。

3. AtomicInteger源码分析

构造函数

private volatile int value;
/*
* AtomicInteger内部声明了一个volatile修饰的变量value用来保存实际值
* 使用带参的构造函数会将入参赋值给value,无参构造器value默认值为0
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}

自增函数

import sun.misc.Unsafe;
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
/*
* 可以看到自增函数中调用了Unsafe函数的getAndAddInt方法
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

那么这个getAndAddInt方法是干嘛的呢,首先来了解一下Unsafe这个类。

Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。
通常我们最好也不要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。

再来说Unsafe的getAndAddInt,通过反编译可以看到实现代码:

/*
* 其中getIntVolatile和compareAndSwapInt都是native方法
* getIntVolatile是获取当前的期望值
* compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
*/
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
{
i = getIntVolatile(paramObject, paramLong);
} while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}

incrementAndGet是将自增后的值返回,还有一个方法getAndIncrement是将自增前的值返回,分别对应++ii++操作。同样的decrementAndGet和getAndDecrement则对--ii--操作。

4. CAS中ABA问题的解决

CAS也并非完美的,它会导致ABA问题,就是说,当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。

那么如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。

在JDK中提供了AtomicStampedReference类来解决这个问题,思路是一样的。这个类也维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。

下面我们通过代码演示来看一下AtomicStampedReference的使用:

package com.wangjun.thread;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference; public class ABA { // 普通的原子类,存在ABA问题
AtomicInteger a1 = new AtomicInteger(10);
// 带有时间戳的原子类,不存在ABA问题,第二个参数就是默认时间戳,这里指定为0
AtomicStampedReference<Integer> a2 = new AtomicStampedReference<Integer>(10, 0); public static void main(String[] args) {
ABA a = new ABA();
a.test();
} public void test() {
new Thread1().start();
new Thread2().start();
new Thread3().start();
new Thread4().start();
} class Thread1 extends Thread {
@Override
public void run() {
a1.compareAndSet(10, 11);
a1.compareAndSet(11, 10);
}
}
class Thread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(200); // 睡0.2秒,给线程1时间做ABA操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AtomicInteger原子操作:" + a1.compareAndSet(10, 11));
}
}
class Thread3 extends Thread {
@Override
public void run() {
try {
Thread.sleep(500); // 睡0.5秒,保证线程4先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = a2.getStamp();
a2.compareAndSet(10, 11, stamp, stamp + 1);
stamp = a2.getStamp();
a2.compareAndSet(11, 10, stamp, stamp + 1);
}
}
class Thread4 extends Thread {
@Override
public void run() {
int stamp = a2.getStamp();
try {
Thread.sleep(1000); // 睡一秒,给线程3时间做ABA操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AtomicStampedReference原子操作:" + a2.compareAndSet(10, 11, stamp, stamp + 1));
}
}
}

可以看到使用AtomicStampedReference进行compareAndSet的时候,除了要验证数据,还要验证时间戳。

如果数据一样,但是时间戳不一样,那么这个数据其实也被修改过了。

参考:

java的Unsafe类:https://www.cnblogs.com/pkufork/p/java_unsafe.html

Java CAS 和ABA问题https://www.cnblogs.com/549294286/p/3766717.html

AtomicInteger原理的更多相关文章

  1. AtomicInteger原理&源码分析

    转自https://www.cnblogs.com/rever/p/8215743.html 深入解析Java AtomicInteger原子类型 在进行并发编程的时候我们需要确保程序在被多个线程并发 ...

  2. Unsafe与CAS

    Unsafe 简单讲一下这个类.Java无法直接访问底层操作系统,而是通过本地(native)方法来访问.不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作 ...

  3. 什么是CAS机制?(转)

    围绕下面四个点展开叙述: 一:什么是CAS机制? 二:Java当中CAS的底层实现 三:CAS的ABA问题和解决方法 四:java8对CAS的优化 一:什么是CAS机制? 我们先看一段代码: 启动两个 ...

  4. 【重学Java】多线程进阶(线程池、原子性、并发工具类)

    线程池 线程状态介绍 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.线程对象在不同的时期有不同的状态.那么Java中的线程存在哪几种状态呢?Java中的线程 状态被定 ...

  5. 对Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  6. Java原子类AtomicInteger实现原理的一点总结

    java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类: java.util.concurrent.atomic.AtomicBoolean java. ...

  7. java的原子类 AtomicInteger 实现原理是什么?

    采用硬件提供原子操作指令实现的,即CAS.每次调用都会先判断预期的值是否符合,才进行写操作,保证数据安全. CAS机制 CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换. ...

  8. java中的原子操作类AtomicInteger及其实现原理

    /** * 一,AtomicInteger 是如何实现原子操作的呢? * * 我们先来看一下getAndIncrement的源代码: * public final int getAndIncremen ...

  9. Java并发集合的实现原理

    本文简要介绍Java并发编程方面常用的类和集合,并介绍下其实现原理. AtomicInteger 可以用原子方式更新int值.类 AtomicBoolean.AtomicInteger.AtomicL ...

随机推荐

  1. Linux 命令实战

    命令登录 ssh  UserName@RemoteIP ssh  seemmo@192.168.0.1 统计文件.目录的数量 统计当前目录下文件数量:ls  -l  |  grep  "^- ...

  2. vscode 基本知识以及如何配置 C++ 环境

    参考: 在用VSCode? 看完这篇文章, 开发效率翻倍!最后一条厉害了~ Visual Studio Code(VS code)你们都在用吗?或许你们需要看一下这篇博文 按下 ctrl+K,再按下 ...

  3. Android 音频播放速率调整实现

    最近接触到的一个项目, 有音频播放.切换播放速率和拖动进度到某处播放的需求 ,由于之前只是见过并没有尝试过切换播放速率 , 于是开始调研并最终实现,下面简单记录一下这次的调研过程. MediaPlay ...

  4. ASE19团队项目 beta阶段 model组 scrum6 记录

    本次会议于12月9日,19时30分在微软北京西二号楼sky garden召开,持续20分钟. 与会人员:Jiyan He, Lei Chai, Linfeng Qi, Xueqing Wu, Kun ...

  5. ant design pro超详细入门教程

    1.Ant Design Pro 初了解 说到ant design pro,得先了解一下ant design是个什么东西?ant design蚂蚁金服基于react打造的一个服务于企业级产品的UI框架 ...

  6. 关闭mysql严格模式

    配置文件my.ini sql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" 修改为 s ...

  7. 日志实时查看、轮询、统计、监控工具 Loggrove

    Loggrove 是对日志文件进行 阅读.轮询.关键词匹配.监控告警.图表展示 的 Web 服务. 下载地址 https://github.com/olajowon/loggrove 超轻组件 Pyt ...

  8. 《浏览器工作原理与实践》<10>作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?

    在上一篇文章中我们讲到了什么是作用域,以及 ES6 是如何通过变量环境和词法环境来同时支持变量提升和块级作用域,在最后我们也提到了如何通过词法环境和变量环境来查找变量,这其中就涉及到作用域链的概念. ...

  9. Linux命令——shutdown、halt、poweroff、reboot、cal、date

    shutdown shutdown在关机的时候会通知所有用户 shutdown –r now 现在重启 shutdown now 现在关机 shutdown +5 过5分钟关机 shutdown –c ...

  10. 排序方法——python

    1.冒泡排序法(Bubble Sort) 比较相邻的元素.如果第一个比第二个大,就交换它们两个: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数: 针对所 ...