原子操作类AtomicInteger详解

为什么需要AtomicInteger原子操作类?

对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。测试代码如下:

 
public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static int count = 0;public static void increase() {count++;}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

这里运行了20个线程,每个线程对count变量进行1000此自增操作,如果上面这段代码能够正常并发的话,最后的结果应该是20000才对,但实际结果却发现每次运行的结果都不相同,都是一个小于20000的数字。这是为什么呢?


要是换成volatile修饰count变量呢?

顺带说下volatile关键字很重要的两个特性:

1、保证变量在线程间可见,对volatile变量所有的写操作都能立即反应到其他线程中,换句话说,volatile变量在各个线程中是一致的(得益于java内存模型—"先行发生原则");

2、禁止指令的重排序优化;

那么换成volatile修饰count变量后,会有什么效果呢? 试一试:

 
public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static volatile int count = 0;public static void increase() {count++;}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

结果似乎又失望了,测试结果和上面的一致,每次都是输出小于20000的数字。这又是为什么么? 上面的论据是正确的,也就是上面标红的内容,但是这个论据并不能得出"基于volatile变量的运算在并发下是安全的"这个结论,因为核心点在于java里的运算(比如自增)并不是原子性的。


用了AtomicInteger类后会变成什么样子呢?

把上面的代码改造成AtomicInteger原子类型,先看看效果

import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerTest {private static final int THREADS_CONUT = 20;public static AtomicInteger count = new AtomicInteger(0);public static void increase() {count.incrementAndGet();}public static void main(String[] args) {Thread[] threads = new Thread[THREADS_CONUT];for (int i = 0; i < THREADS_CONUT; i++) {threads[i] = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {increase();}}});threads[i].start();}while (Thread.activeCount() > 1) {Thread.yield();}System.out.println(count);}}

结果每次都输出20000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。


非阻塞同步

同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。

我们知道,阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题,那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。在硬件指令集的发展驱动下,使得 "操作和冲突检测" 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。《深入理解Java虚拟机第二版.周志明》第十三章中这样描述关于CAS机制:

图取自《深入理解Java虚拟机第二版.周志明》13.2.2

所以再返回来看AtomicInteger.incrementAndGet()方法,它的时间也比较简单

 
  1. /*** Atomically increments by one the current value.** @return the updated value*/public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}}

     

  2.  

incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:

 
/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return true if successful. False return indicates that* the actual value was not equal to the expected value.*/public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

可以看到,compareAndSet()调用的就是Unsafe.compareAndSwapInt()方法,即Unsafe类的CAS操作。


使用示例如下图,用于标识程序执行过程中是否发生了异常,使用quartz实现高级定制化定时任务(包含管理界面)实现中:


2020-02-12补充如下内容:

  • 原子类相比于普通的锁,粒度更细、效率更高(除了高度竞争的情况下)
  • 如果对于上面的示例代码中使用了thread.yield()之类的方法不清晰的,可以直接看下面的代码压测:
 
public class AtomicIntegerTest implements Runnable {static AtomicInteger atomicInteger = new AtomicInteger(0);static int commonInteger = 0;public void addAtomicInteger() {atomicInteger.getAndIncrement();}public void addCommonInteger() {commonInteger++;}@Overridepublic void run() {//可以调大10000看效果更明显for (int i = 0; i < 10000; i++) {addAtomicInteger();addCommonInteger();}}public static void main(String[] args) throws InterruptedException {AtomicIntegerTest atomicIntegerTest = new AtomicIntegerTest();Thread thread1 = new Thread(atomicIntegerTest);Thread thread2 = new Thread(atomicIntegerTest);thread1.start();thread2.start();//join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕thread1.join();thread2.join();System.out.println("AtomicInteger add result = " + atomicInteger.get());System.out.println("CommonInteger add result = " + commonInteger);}}
  • 原子类一览图参考如下:

  • 如何把普通变量升级为原子变量?主要是AtomicIntegerFieldUpdater<T>类,参考如下代码:
 
  1. /*** @description 将普通变量升级为原子变量**/public class AtomicIntegerFieldUpdaterTest implements Runnable {static Goods phone;static Goods computer;AtomicIntegerFieldUpdater<Goods> atomicIntegerFieldUpdater =AtomicIntegerFieldUpdater.newUpdater(Goods.class, "price");@Overridepublic void run() {for (int i = 0; i < 10000; i++) {phone.price++;atomicIntegerFieldUpdater.getAndIncrement(computer);}}static class Goods {//商品定价volatile int price;}public static void main(String[] args) throws InterruptedException {phone = new Goods();computer = new Goods();AtomicIntegerFieldUpdaterTest atomicIntegerFieldUpdaterTest = new AtomicIntegerFieldUpdaterTest();Thread thread1 = new Thread(atomicIntegerFieldUpdaterTest);Thread thread2 = new Thread(atomicIntegerFieldUpdaterTest);thread1.start();thread2.start();//join()方法是为了让main主线程等待thread1、thread2两个子线程执行完毕thread1.join();thread2.join();System.out.println("CommonInteger price = " + phone.price);System.out.println("AtomicInteger price = " + computer.price);}}

     

  • 在高并发情况下,LongAdder(累加器)比AtomicLong原子操作效率更高,LongAdder累加器是java8新加入的,参考以下压测代码:
 
  1. /*** @description 压测AtomicLong的原子操作性能**/public class AtomicLongTest implements Runnable {private static AtomicLong atomicLong = new AtomicLong(0);@Overridepublic void run() {for (int i = 0; i < 10000; i++) {atomicLong.incrementAndGet();}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(30);long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {es.submit(new AtomicLongTest());}es.shutdown();//保证任务全部执行完while (!es.isTerminated()) { }long end = System.currentTimeMillis();System.out.println("AtomicLong add 耗时=" + (end - start));System.out.println("AtomicLong add result=" + atomicLong.get());}}/*** @description 压测LongAdder的原子操作性能**/public class LongAdderTest implements Runnable {private static LongAdder longAdder = new LongAdder();@Overridepublic void run() {for (int i = 0; i < 10000; i++) {longAdder.increment();}}public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(30);long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {es.submit(new LongAdderTest());}es.shutdown();//保证任务全部执行完while (!es.isTerminated()) {}long end = System.currentTimeMillis();System.out.println("LongAdder add 耗时=" + (end - start));System.out.println("LongAdder add result=" + longAdder.sum());}}

     

  2.  

在高度并发竞争情形下,AtomicLong每次进行add都需要flush和refresh(这一块涉及到java内存模型中的工作内存和主内存的,所有变量操作只能在工作内存中进行,然后写回主内存,其它线程再次读取新值),每次add()都需要同步,在高并发时会有比较多冲突,比较耗时导致效率低;而LongAdder中每个线程会维护自己的一个计数器,在最后执行LongAdder.sum()方法时候才需要同步,把所有计数器全部加起来,不需要flush和refresh操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386526.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动端Rem之讲解总结

日妈常说的H5页面&#xff0c;为啥叫H5页面嘛&#xff0c;不就是手机上展示的页面吗&#xff1f;那是因为啊手机兼容所有html5新特性&#xff0c;所以跑在手机上的页面也叫h5页面&#xff0c;跨平台&#xff08;安装ios),基于webview&#xff0c;它就是终端开发的一个组件&…

终于有人把安卓程序员必学知识点全整理出来了,送大厂面经一份!

除了Bug&#xff0c;最让你头疼的问题是什么&#xff1f;单身&#xff1f;秃头&#xff1f;996?面试造火箭&#xff0c;工作拧螺丝&#xff1f; 作为安卓开发者&#xff0c;除了Bug&#xff0c;经常会碰到下面这些问题&#xff1a; 应用卡顿&#xff0c;丢帧&#xff0c;屏幕画…

mq引入以后的缺点

系统可用性降低? 一旦mq不能使用以后,系统A不能发送消息到mq,系统BCD无法从mq中获取到消息.整个系统就崩溃了. 如何解决: 系统复杂程度增加? 加入mq以后,mq引入来的问题很多,然后导致系统的复杂程度增加. 如何解决 系统的一致性降低? 有人给系统A发送了一个请求,本来这个请求…

网易云的朋友给我这份339页的Android面经,成功入职阿里

IT行业的前景 近几年来&#xff0c;大数据、人工智能AI、物联网等一些技术不断发展&#xff0c;也让人们看到了IT行业的繁荣与良好的前景。越来越多的高校学府加大了对计算机的投入&#xff0c;设立相应的热门专业来吸引招生。当然也有越来越多的人选择从事这个行业&#xff0…

git介绍和常用操作

转载于:https://www.cnblogs.com/kesz/p/11124423.html

网易云的朋友给我这份339页的Android面经,满满干货指导

想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样~。 25%的面试官会在头5分钟内决定面试的结果60%的面试官会在头15分钟内决定面试的结果 一般来说&#xff0c;一场单面的时间在30分钟左右&…

synchronized 和Lock区别

synchronized实现原理 Java中每一个对象都可以作为锁&#xff0c;这是synchronized实现同步的基础&#xff1a; 普通同步方法&#xff0c;锁是当前实例对象静态同步方法&#xff0c;锁是当前类的class对象同步方法块&#xff0c;锁是括号里面的对象 当一个线程访问同步代码块…

美团安卓面试,难道Android真的凉了?快来收藏!

我所接触的Android开发者&#xff0c;百分之九十五以上 都遇到了以下几点致命弱点&#xff01; 如果这些问题也是阻止你升职加薪&#xff0c;跳槽大厂的阻碍。 那么我确信可以帮你突破瓶颈&#xff01; 1.开发者的门越来越高&#xff1a; 小厂的机会少了&#xff0c;大厂…

django -- 实现ORM登录

前戏 上篇文章写了一个简单的登录页面&#xff0c;那我们可不可以实现一个简单的登录功能呢&#xff1f;如果登录成功&#xff0c;给返回一个页面&#xff0c;失败给出错误的提示呢&#xff1f; 在之前学HTML的时候&#xff0c;我们知道&#xff0c;网页在往服务器提交数据的时…

美团点评APP在移动网络性能优化的实践,通用流行框架大全

" 对于程序员来说&#xff0c;如果哪一天开始他停止了学习&#xff0c;那么他的职业生涯便开始宣告消亡。” 高薪的IT行业是众多年轻人的职业梦想&#xff0c;然而&#xff0c;一旦身入其中却发觉没有想像中那么美好。被称为IT蓝领的编程员&#xff0c;工作强度大&#xf…

centos7.0利用yum快速安装mysql8.0

我这里直接使用MySQL Yum存储库的方式快速安装&#xff1a; 抽象 MySQL Yum存储库提供用于在Linux平台上安装MySQL服务器&#xff0c;客户端和其他组件的RPM包。这些软件包还可以升级和替换从Linux发行版本机软件存储库安装的任何第三方MySQL软件包&#xff0c;如果可以从MySQL…

腾讯3轮面试都问了Android事件分发,论程序员成长的正确姿势

前言 这些题目是网友去美团等一线互联网公司面试被问到的题目。笔者从自身面试经历、各大网络社交技术平台搜集整理而成&#xff0c;熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。 主要分为以下几部分&#xff1a; &#xff08;1&#xff09;Android面试题 …

happens-before规则和as-if-serial语义

概述 本文大部分整理自《Java并发编程的艺术》&#xff0c;温故而知新&#xff0c;加深对基础的理解程度。 指令序列的重排序 我们在编写代码的时候&#xff0c;通常自上而下编写&#xff0c;那么希望执行的顺序&#xff0c;理论上也是逐步串行执行&#xff0c;但是为了提高…

贴片晶振无源石英谐振器直插晶振

贴片晶振 贴片晶振3.579M~25MHz无源石英谐振器直插晶振 文章目录 贴片晶振前言一、贴片晶振3.579M~25MHz无源石英谐振器直插晶振二、属性三、技术参数总结前言 贴片晶振(Surface Mount Crystal Oscillator)是一种采用表面贴装技术进行安装的晶振。它的主要特点是封装小巧、安…

这些新技术你们都知道吗?成功收获美团,小米安卓offer

前言 近期被两则消息刷屏&#xff0c;【字节跳动持续大规模招聘&#xff0c;全年校招超过1万人】【腾讯有史以来最大规模的校招启动】当然Android岗位也包含在内&#xff0c;因此Android还是有很多机会的。结合往期面试的同学&#xff08;主要是校招&#xff09;经验&#xff…

这些新技术你们都知道吗?看这一篇就够了!

前言 现在已经进入招聘季节&#xff0c;本篇文章旨在分享知名互联网企业面试官面试方法和心得&#xff0c;希望通过本文的阅读能给程序员带来不一样的面试体验和感受&#xff0c;放松面试心态&#xff0c;积极备战&#xff01; 面试题 PS&#xff1a;由于文章篇幅问题&#x…

这份1307页Android面试全套真题解析,源码+原理+手写框架

前言 前不久&#xff0c;几个朋友聚会&#xff0c;谈到了现在的后辈&#xff0c;我就说起了那个大三就已经拿到网易offer的小学弟。 这个学弟是00后&#xff0c;专升本进入我们学校的。进来后就非常努力&#xff0c;每次上课都是第一个到教室的&#xff0c;每次都是坐第一排&…

[转]OpenContrail 体系架构文档

OpenContrail 体系架构文档英文原文&#xff1a;http://opencontrail.org/opencontrail-architecture-documentation/ 翻译者&#xff1a;KkBLuE知行合一 其微信号&#xff1a;kkbluepublic&#xff0c; SDNAP.com翻译整理 OpenContrail 体系架构文档 1 概述 1.1 使用案例 1…

这份354页笔记的Android进阶知识+大厂高频面试题,绝对干货

程序员与别的专业有所不同&#xff0c;其他专业都是越老越香&#xff0c;而程序员却是一个例外&#xff0c;因为计算机技术更新太快&#xff0c;而且工作强度很大&#xff0c;因此大部分程序员只会写 3 年代码。3 年后要不晋升做项目经理&#xff0c;要么转行&#xff0c;个别研…

这是一份用心整理的Android面试总结,聪明人已经收藏了!

前言 本文想分享的是如何准备阿里面试的以及面试过程的所想所得&#xff0c;希望能帮到你。 首先&#xff0c;可能要让你们失望的是&#xff0c;这篇文章不会有大篇幅的面试题答案。如果想要看这方面的内容&#xff0c;可以看我之前的文章。感谢关注 很多人准备面试的时候&a…