例子

i++的简单流程

众所周知,i++分为三步:

1. 读取i的值

2. 计算i+1

3. 将计算出i+1赋给i

保证i++操作的线程安全

用锁和volatile

可以使用锁来保持操作的原子性和变量可见性,用volatile保持值的可见性和操作顺序性;

从一个小例子引发的JAVA内存可见性的简单思考和猜想以及DCL单例模式中的VOLATILE的作用:https://www.cnblogs.com/theRhyme/p/12145461.html

用java.util.concurrent.atomic包下的原子类

如果仅仅是计算操作,我们自然就想到了java.util.concurrent.atomic包下的原子类,则不必考虑锁的升级、获取、释放等消耗,也不必考虑锁的粒度、种类、可重入性等;

由于atomic由于底层是Unsafe对象的CAS操作,缺点也很明显:需要循环时间开销,只能是单个变量CAS,ABA问题(通过AtomicStampedReference解决——增加了stamp类似于version标识)。

AtomicInteger方法源码

incrementAndGet方法源码

public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

incrementAndGet,先increment,再get,所以获取的是increment后的值,而unsafe.getAndAddInt先get,所以这里需要"+1";

valueOffset是什么呢?

  private static final long valueOffset;

    static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

AtomicInteger的静态属性valueOffset,属性value的偏移量,在类加载的时候,通过AtomicInteger的Field——"value"初始化,后续通过当前的AtomicInteger实例和该valueOffset obtain该实例value属性的值;

个人对valueOffset的理解

如果想获取一个对象的属性的值,我们一般通过getter方法获得,而sun.misc.Unsafe却不同,我们可以把一个对象实例想象成一块内存,而这块内存中包含了一些属性,如何获取这块内存中的某个属性呢?那就需要该属性在该内存的偏移量了,每个属性在该对象内存中valueOffset偏移量不同,每个属性的偏移量在该类加载或者之前就已经确定了(则该类的不同实例的同一属性偏移量相等),所以sun.misc.Unsafe可以通过一个对象实例该属性的偏移量用原语获得该对象对应属性的值;

sun.misc.Unsafe#getAndAddInt IDEA反编译后的源码(与JMM相呼应)

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;
}

unsafe.getAndAddInt具体实现是循环不停的compare主存中取到的值var5(this.getIntVolatile)和当前的线程的工作内存中的值(通过对象实例var1和偏移量var2获得),直到两者equal(保证原子性)则更新为新值var5+var4(delta) ,从这里我们也体会到了Java内存模型(线程不能直接操作主存中的值,需要复制一份到自己的工作内存中)。

getIntVolatile(主存)和compareAndSwapInt都是sun.misc.Unsafe的native方法。

总结

AtomicInteger通过Unsafe对象保证原子性,而Unsafe对象的getAndAddInt方法通过循环比较主存和线程工作内存中的属性值相等后更新(即CAS)来保证原子性;

AtomicInteger的value属性也被volatile关键字修饰:volatile关键字的作用

private volatile int value;

保证i++原子性的几种方式

下面是一个非常非常简单的小例子,分别是非线程安全的i++,锁同步以及Atomic Class:

public class Counter {
@Getter
private volatile int i = 0; @Getter
private volatile AtomicInteger atomicInteger = new AtomicInteger(0); public void increment(){
i++;
// 1. 读取i的值
// 2. 计算i+1
// 3. 把i+1的值赋给i
} public void incrementSync(){
synchronized(this) {
i++;
// 1. 读取i的值
// 2. 计算i+1
// 3. 把i+1的值赋给i
}
} public void incrementAtomic(){
// 先increment再返回
atomicInteger.incrementAndGet();
}
}

测试类:

public class AtomicityTest {
private Counter counter;
/**
* 每个线程打印的次数
*/
private int count; @Before
public void init(){
counter = new Counter();
count = 10000;
} /**
* 非线程安全的i++
*/
@Test
public void increment() throws InterruptedException { Thread t1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.increment();
}
}); Thread t2 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.increment();
}
}); t1.start();
t2.start(); // 单元测试必须新起的线程要在主线程里join,否则主线程运行完毕,新起的线程还执行完
t1.join();
t2.join();
/*
ThreeParamsEquals<Enum, Enum, Enum> equals = (Enum p1, Enum p2, Enum p3) -> p1.equals(p2) && p1.equals(p3);
while (true){
if (equals.equals(Thread.State.TERMINATED, t1.getState(), t2.getState())) {
break;
}
}*/
System.out.println(t1.getState());
System.out.println(t2.getState());
// 由于不是原子性操作,两个线程执行总共20000次i++,结果i的值都小于20000
System.out.println(counter.getI());
} /**
* 线程安全的i++
*/
@Test
public void incrementSync() throws InterruptedException { Thread t1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incrementSync();
}
}); Thread t2 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incrementSync();
}
}); t1.start();
t2.start(); t1.join();
t2.join(); System.out.println(t1.getState());
System.out.println(t2.getState());
// 由于保证了原子性,顺序性,可见性操作,两个线程执行总共20000次i++,结果i的值都等于20000
System.out.println(counter.getI());
} /**
* 原子类AtomicInteger
*/
@Test
public void incrementAtomic() throws InterruptedException { Thread t1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incrementAtomic();
}
}); Thread t2 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incrementAtomic();
}
}); t1.start();
t2.start(); t1.join();
t2.join(); System.out.println(t1.getState());
System.out.println(t2.getState());
// 由于保证了原子性,顺序性,可见性操作,两个线程执行总共20000次i++,结果i的值都等于20000
System.out.println(counter.getAtomicInteger());
}
}

从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法源码介绍,valueOffset偏移量的理解的更多相关文章

  1. 全面理解Java内存模型(JMM)及volatile关键字(转载)

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

  2. 全面理解Java内存模型(JMM)及volatile关键字(转)

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  3. 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)

    并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...

  4. 深入理解java内存模型

    深入理解Java内存模型(一)——基础 深入理解Java内存模型(二)——重排序 深入理解Java内存模型(三)——顺序一致性 深入理解Java内存模型(四)——volatile 深入理解Java内存 ...

  5. 深入理解Java内存模型之系列篇

    深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来 ...

  6. 深入理解java内存模型系列文章

    转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...

  7. 【Todo】【转载】深入理解Java内存模型

    提纲挈领地说一下Java内存模型: 什么是Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范.Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几 ...

  8. 深入理解Java内存模型(一)——基础(转)

    转自程晓明的"深入理解Java内存模型"的博客 http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发 ...

  9. 深入理解Java内存模型之系列篇[转]

    原文链接:http://blog.csdn.net/ccit0519/article/details/11241403 深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需 ...

随机推荐

  1. 转: zabbix对cisco2960的监控

    转自:http://blog.chinaunix.net/uid-12115233-id-3561954.html 1:首先在官网下载Cisco2950 模板https://www.zabbix.co ...

  2. Replace into 与Insert into on duplicate key update的区别

    前提条件:除非表有一个PRIMARY KEY或UNIQUE索引,否则,使用这2条语句没有意义.该语句会与INSERT相同 1. Replace into (1)   添加相同的主键 操作前       ...

  3. 使用JavaServer Faces技术的Web模块:hello1 example

    该hello1应用程序是一个Web模块,它使用JavaServer Faces技术来显示问候语和响应.您可以使用文本编辑器查看应用程序文件,也可以使用NetBeans IDE. 此应用程序的源代码位于 ...

  4. JXJJOI2018_T3_catch

    题目描述 Lemon因为偶然的原因,当上了警察局长.而一上任,他就碰到了个大麻烦:追捕周克华. 周克华是人尽皆知的抢劫杀人犯,而就在几天前,他在Lemon辖区内的银行门口,枪杀了一名储户后逃之夭夭.L ...

  5. [洛谷P4388] 付公主的矩形

    18.09.09模拟赛T1. 一道数学题. 题目传送门 首先把对角线当成是某个点的移动轨迹,从左下到右上. 那么这个点每上升一个单位长度,就穿过一个格子. 每右移一个单位长度,也会穿过一个格子. 例外 ...

  6. 在 macOS 上试用 Gentoo/Prefix

    前几天参加了许朋程主讲的Tunight,对Gentoo有了一定的了解,不过看到如此复杂的安装过程和长久的编译时间,又看看我的CPU,只能望而却步了.不过,有Gentoo/Prefix这个工具,使得我们 ...

  7. uploadifive如何动态传参

    直接上代码 关键:$('#file_upload').data('uploadifive').settings.formData = { 'ID': 'ceshi'}; //动态更改formData的 ...

  8. JavaScript中点击按钮弹出新的浏览器窗口

    */ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.js * 作者:常轩 * 微信公众号:Worldhel ...

  9. 下一代网际协议IPv6

    下一代网际协议IPv6 一.解决 IP 地址耗尽的措施 从计算机本身发展以及从因特网规模和网络传输速率来看,现在 IPv4 已很不适用. 最主要的问题就是 32 位的 IP 地址不够用. 在 2019 ...

  10. 解决Request中参数中文乱码问题

    1.使用配置过滤器的方式解决 在web.xml中增加过滤器: <!--配置解决中文乱码的过滤器--> <filter> <filter-name>character ...