例子

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. dubbo分布式框架下web层调用业务层一直报空指针异常的解决办法

    java.lang.NullPointerException............... 环境:SSM(通用mapper)+Dubbo 1.检查导包 提示注解@Reference  应该导入  im ...

  2. windows版 Sublime Text 2 快捷键

    ucifr 翻译了 Sublime Text 2 快捷键 Mac版,用win系统的哥们表示伤不起啊~ 今天把windows版 Sublime Text 2 快捷键 整理了出来,与众兄弟们分享: Ctr ...

  3. 图文并解Word插入修改删除批注

    .插入批注 首先选择对象,比如部分文字[hd1] ,之后执行这样的操作:"插入"→"批注":插入的批注处于编辑状态,可以直接输入批注的文字即可;图解如下: .修 ...

  4. python jQuery筛选器

    筛选器:$(this).next() 下一个    $(this).prev  上一个    $(this).parent()  父     $(this).children() 孩     $(th ...

  5. python基础实现简单的shell sed 替换功能

    #coding:utf-8 from pygame.draw import lines import sys,os old_file = sys.argv[1] #接受外部设备上的参数 new_fil ...

  6. LLDB奇巧淫技

    打印视图层级 这个相信很多人都会了,是ta是ta就是ta recursiveDescription 用法大概就是如下 123 po [self.view recursiveDescription] p ...

  7. POI之下载模板(或各种文件)

    该例基于Nutz框架 前台代码: <a href="" id="errordownload" onclick="downloadErrorLog ...

  8. C++走向远洋——30(六周,项目一1.0)

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

  9. win10 pycharm调试技巧 Debug

    1.设置断点 2.调试方法对比 step into:单步执行,遇到子函数就进入并且继续单步执行(简而言之,进入子函数): step over:在单步执行时,在函数内遇到子函数时不会进入子函数内单步执行 ...

  10. UBB代码

    UBB代码是HTML(标准通用标记语言下的一个应用)的一个变种,是Ultimate Bulletin Board (国外的一个BBS程序)采用的一种特殊的TAG.您也许已经对它很熟悉了.UBB代码很简 ...