例子

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. 吴裕雄--天生自然 R语言开发学习:使用ggplot2进行高级绘图(续二)

    #----------------------------------------------------------# # R in Action (2nd ed): Chapter 19 # # ...

  2. Eclipse快速入门:远程调试Java应用

    Eclipse快速入门:远程调试Java应用 2012年03月27日00:00 it168网站原创 作者:皮丽华 编辑:皮丽华 我要评论(0) 标签: Eclipse , Java , Java框架, ...

  3. HTTP接口抓包工具之Fiddler

    Fiddler的基本功能介绍: Fiddler是最强大最好用的Web调试工具之一,它能记录所有客户端和服务器的http和https请求,允许你监视,设置断点,甚至修改输入输出数据,Fiddler包含了 ...

  4. TCP-IP-part7-IP协议相关技术(一)

    仅凭IP是无法完成通信的,需要一些IP的辅助技术.这些技术的包格式可能不一样,但它们都是基于IP地址进行的,都是通过匹配路由表来进行的,只是功能不一样.例如DHCP分配IP地址,它只管通知这条信息,具 ...

  5. failed to load main-class manifest attribute(运行jar包出错)

    原因描述:MANIFEST.MF文件中的Main-Class配置不正确或格式不正确 检查方式:以WinRarR的方式打开jar包,如图所示, 点击进入箭头所指的META-INF文件夹     将MAN ...

  6. centos上安装python环境

    1.安装python-pip ​ 首先安装epel扩展源: ​ yum -y install epel-release ​ 更新完成之后,安装pip: ​ yum -y install python- ...

  7. 从TP-Link到雷蛇,纷纷入局智能手机业到底想干什么?

    ​   "眼看他起朱楼,眼看他宴宾客,眼看他楼塌了",这句形容世态炎凉的话其实与智能手机市场更为相像.诺基亚的辉煌与没落.黑莓的强势与消声无迹.摩托罗拉的数次易手.小米的横空出世与 ...

  8. kettle_errot_karafLifecycleListenter

    使用kettle 6.1 通过命令行批量执行作业的过程中,发现偶尔有作业执行时间会变慢几分钟,查看日志发现改作业开始就报了一个错 报错之后才会继续下面的作业,虽然不影响最终作业执行结果,但也延误了一些 ...

  9. oracle监控参数

    Sar –u 检查CPU的繁忙程度列说明Usr用户模式下cpu运行所占的百分比Sys系统模式下cpu运行所占的百分比Wio因为有进程等待块I/O而使cpu处于闲置状态所占百分比IdleCpu为闲置状态 ...

  10. python爬虫-scrapy日志

    1.scrapy日志介绍 Scrapy的日志系统是实现了对python内置的日志的封装 scrapy也使用python日志级别分类 logging.CRITICAL logging.ERROE log ...