从原子类和Unsafe来理解Java内存模型,AtomicInteger的incrementAndGet方法源码介绍,valueOffset偏移量的理解
例子
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偏移量的理解的更多相关文章
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)
并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...
- 深入理解java内存模型
深入理解Java内存模型(一)——基础 深入理解Java内存模型(二)——重排序 深入理解Java内存模型(三)——顺序一致性 深入理解Java内存模型(四)——volatile 深入理解Java内存 ...
- 深入理解Java内存模型之系列篇
深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来 ...
- 深入理解java内存模型系列文章
转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...
- 【Todo】【转载】深入理解Java内存模型
提纲挈领地说一下Java内存模型: 什么是Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范.Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几 ...
- 深入理解Java内存模型(一)——基础(转)
转自程晓明的"深入理解Java内存模型"的博客 http://www.infoq.com/cn/articles/java-memory-model-1 并发编程模型的分类 在并发 ...
- 深入理解Java内存模型之系列篇[转]
原文链接:http://blog.csdn.net/ccit0519/article/details/11241403 深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需 ...
随机推荐
- 传统if 从句子——以条件表达式作为if条件
传统if 从句子——以条件表达式作为 if条件if [ 条件表达式 ]then command command commandelse command commandfi 条件表达式 文件表达式 ...
- ltp压力测试结果分析脚本
最近工作性质发生了改变,在做操作系统方面的测试.接手的第一个任务是做ltp stress.测试内核稳定性. 做完之后会结果进行统计分析.因为统计的内容比较多,都是通过shell命令行进行操作.于是编写 ...
- leetcode笔记——35.搜索插入位置 - CrowFea
0.问题描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引.如果目标值不存在于数组中,返回它将会被按顺序插入的位置. 你可以假设数组中无重复元素. 示例 1: 12 输入: [1,3 ...
- 一劳永逸的解决AFNetworking3.0网络请求问题
AFNetworking在iOS网络请求第三方库中占据着半壁江山,前段时间将AFNetworking进行了3.0版本的迁移,运用面向对象的设计将代码进行封装整合,这篇文章主要为还在寻找AFNetwor ...
- ubuntu16.04问题 · 最初的梦想
ubuntu 包管理器命令 1 $ sudo synaptic 安装主题 1 $ sudo apt-get install unity-tweak-tool 下载主题 https://www.sysg ...
- 用hugo建博客的记录 · 老张不服老
前后累计折腾近6个小时,总算把搭建hugo静态博客的整个过程搞清楚了.为什么用了这么久?主要还是想偷懒,不喜欢读英文说明.那就用中文记录一下过程吧.还是中文顺眼啊. 某日发现自己有展示些东西给外网的需 ...
- 使用Python生成自己的特色二维码
二维码又称二维条码,常见的二维码为QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据 ...
- C++走向远洋——55(项目一3、分数类的重载、>>
*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:text.cpp * 作者:常轩 * 微信公众号:Worldhe ...
- 使用Properties配置文件进行配置读取
#使用Properties配置文件进行配置读取: 例如:有一个配置文件的内容如下: # setting.properties last_open_file=/data/hello.txt auto_s ...
- LeetCode--链表3-经典问题
LeetCode--链表3-经典问题 题1 反转链表 第一次代码超出时间限制 原因是,反转之后链表的尾部节点和头结点连上了 /** * Definition for singly-linked lis ...