概述:

  早期的JDK版本中,如果要并发的对Integer、Long、Double之类的Java原始类型或引用类型进行操作,一般都需要通过锁来控制并发,以防止数据不一致。JUC-Atomic原子类位于java.util.concurrent.atomic包下。该包提供了许多Java原始/引用类型的映射类。如AtomicIntegerAtomicLongAtomicBoolean,这些类可以通过一种“无锁算法”,线程安全的操作Integer、Long、Boolean等原始类型。


包中类分为五种:

  基本类型:

  • AtomicBoolean:布尔型原子类
  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类

  数组:

  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray:引用类型数组原子类

  引用类型:

  • AtomicReference:引用类型原子类
  • AtomicStampedRerence:原子更新引用类型里的字段原子类
  • AtomicMarkableReference:原子更新带有标记位的引用类型

  对象的属性:

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新长整形字段的更新器
  • AtomicReferenceFieldUpdater:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题

  本文不会详细介绍这几种类型的api及使用,只是列出Atomic的实现原理,及比较重点的类


基本类型原子类: 

  • AtomicBoolean:布尔型原子类
  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类

  这几个类的共同特点是都提供单个变量的原子方式访问和更新功能。以AtomicLong为代表,进行介绍。

我们使用AtomicLong来演示之前的线程不安全的例子:

/**
* 并发测试代码
*/
@ThreadSafe
public class AtomicExample2 {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
//变成了AtomicLong类型
public static AtomicLong count = new AtomicLong(0);
public static void main(String[] args) throws InterruptedException {
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器 闭锁
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
executorService.execute(() ->{
try {
semaphore.acquire();
add();
//释放
semaphore.release();
} catch (Exception e) {
System.out.println("exception:"+e.getMessage());
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count:{}"+count.get());
}
private static void add(){
count.incrementAndGet();//先做增加再获取当前值
//count.getAndIncrement();先获取当前值再做增加
}
}

当使用AtomicLong去执行自增操作时,得出的最终结果count就是5000。数次运行情况下结果一致。不会带来线程不安全的情况。那我们来看看AtomicLong是如何保证线程安全的呢。

我们看看incrementAndGet方法,看看AtomicLong如何实现单个变量的原子方式更新。Unsafe是CAS的核心类,AtomicLong是基于CAS实现的。此处就介绍AtomicLong,AtomicBoolean、AtomicInteger、AtomicReference与之相似,就不一一介绍

private static final Unsafe unsafe = Unsafe.getUnsafe();

public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

incrementAndGet方法实际上是调用Unsafe类的方法来执行操作,我们进入Unsafe里看看具体的getAndAddLong是如何实现原子方式更新的。

public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}

我们来解析一下这个方法,var1为当前调用这个方法的对象,var2是当前值,假如执行的2+1=3的操作,那么var4就是1。var6是调用底层方法获得底层当前值。假设没有其他线程来处理count,那么var6就是var2。此处使用了一个do while循环。compareAndSwapLong方法是native的,代表是java底层的方法。也是遵循CAS算法的api。compareAndSwap,比较并交换。在getAndAddLong的while判断中,该方法实现的是:

  对于var1这个对象,如果当前值var2和底层值var6相同的话,就更新为后面的操作结果值。当我们执行更新结果时,可能被其他线程修改,因此此处判断当前值与期望值相同时才允许更新。否则重新取出当前的底层值,和当前count的值再做比较。保证当前值与底层值完全一致时才进行结果更新,以此保证线程安全。这也是Atomic使用CAS原理实现的机制。底层值是主内存中的值,当前值是源自于工作内存。

  由于该方法的逻辑是采用自旋的方式不断更新目标值,直到更新成功,在并发量较低的环境下,线程冲突较少,自旋次数不会很多。但是在高并发情况下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时的AtomicLong的自旋会成为瓶颈,因此为了解决高并发环境下的AtomicLong的自旋瓶颈问题,引入了LongAdder。


LongAdder:

  AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

  低并发、一般的业务场景下AtomicLong是足够了。如果并发量很多,存在大量写多读少的情况,那LongAdder可能更合适。


AtomicBoolean:

  针对该类我们主要研究compareAndSet函数

public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

  该函数实现的功能是高并发情况下只有一个线程能访问这个属性值,常用于初始化一次的功能中。

private static AtomicBoolean initialized = new AtomicBoolean(false);
public void init()
{
if( initialized.compareAndSet(false, true) )//如果为false,更新为true
{
// 初始化操作代码....
}
}

  各原子类api及使用demo,可以参考:https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/Multithread/Atomic.md

  主要是掌握CAS算法的设计思想,了解原子类如何保证原子操作。

并发编程-JUC之Atomic的更多相关文章

  1. 并发编程JUC系列AQS(CountDownLatch、CyclicBarrier、Semaphore)

    一.CountDownLatch package com.jonychen.test; import java.util.concurrent.CountDownLatch; import java. ...

  2. java并发编程JUC第九篇:CountDownLatch线程同步

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  3. java并发编程JUC第十篇:CyclicBarrier线程同步

    在之前的文章中已经为大家介绍了java并发编程的工具:BlockingQueue接口.ArrayBlockingQueue.DelayQueue.LinkedBlockingQueue.Priorit ...

  4. C++11并发编程:原子操作atomic

    一:概述 项目中经常用遇到多线程操作共享数据问题,常用的处理方式是对共享数据进行加锁,如果多线程操作共享变量也同样采用这种方式. 为什么要对共享变量加锁或使用原子操作?如两个线程操作同一变量过程中,一 ...

  5. 并发编程之原子操作Atomic&Unsafe

    原子操作:不能被分割(中断)的一个或一系列操作叫原子操作. 原子操作Atomic主要有12个类,4种类型的原子更新方式,原子更新基本类型,原子更新数组,原子更新字段,原子更新引用.Atomic包中的类 ...

  6. 并发编程之原子Atomic&Unsafe

    1.原子更新基本类型类   用于通过原子的方式更新基本类型,Atomic包提供了以下三个类: AtomicBoolean:原子更新布尔类型. AtomicInteger:原子更新整型. AtomicL ...

  7. java并发编程JUC第十二篇:AtomicInteger原子整型

    AtomicInteger 类底层存储一个int值,并提供方法对该int值进行原子操作.AtomicInteger 作为java.util.concurrent.atomic包的一部分,从Java 1 ...

  8. java并发编程JUC第十一篇:如何在线程之间进行对等数据交换

    java.util.concurrent.Exchanger可以用来进行数据交换,或者被称为"数据交换器".两个线程可以使用Exchanger交换数据,下图用来说明Exchange ...

  9. Java 面试知识点解析(二)——高并发编程篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

随机推荐

  1. Mybatis源码之StatementType

      在mybatis中StatementType的值决定了由什么对象来执行我们的SQL语句.本文来分析下在mybatis中具体是怎么处理的. StatementType 1.StatementType ...

  2. Centos7-yum部署配置LNMP+php-fgm,一台机器上部署

    一.简介 1.了解nginx特性 请参考,https://www.cnblogs.com/zhangxingeng/p/10150955.html 2.LNMP:linux+nginx+mysql+p ...

  3. 第10章 使用密码保护API - Identity Server 4 中文文档(v1.0.0)

    OAuth 2.0资源所有者密码授权允许客户端向令牌服务发送用户名和密码,并获取代表该用户的访问令牌. 除了无法承载浏览器的旧应用程序之外,规范通常建议不要使用资源所有者密码授予.一般来说,当您要对用 ...

  4. 学JAVA第六天,运算符、表达式、if语句以及for、while、都循环

    今天老师讲的内容有点多,但是都是在学C#时学过的,用法都差不多,所以很好理解. 算术运算符:+, - ,* , / ,% ,++  ,-- 关系运算符:>,<,>=,<=,== ...

  5. es6的let,const

    1.es6 新增的let const 命令 let用来定义一个局部变量,故名思意就是只在当前代码块可用 1.1 let 声明的变量不存在变量提升(var 声明的变量存在变量提升)且代码块内 暂时性死区 ...

  6. 流程控制值while 循环

    一.结束循环的两种方式 1. 修改条件tag=Truewhile tag: print(1) print(2) print(3) tag=False print(4) 2.while + break ...

  7. HTML和CSS前端教程03-CSS文本样式

    目录 1.CSS颜色-建议就用十六进制 2.CSS长度的度量单位-建议就用px 3.CSS文本样式 3.1. 字体属性 3.1. 文本样式 1.CSS颜色-建议就用十六进制 p{ color: #ff ...

  8. SAP MM 事务代码MI31之思考

    SAP MM 事务代码MI31之思考 1 - MI01之痛 多年SAP项目实施实践中,笔者之前对于SAP系统里盘点凭证创建(MI01)事务代码里的输入界面很是不爽: 第一,MI01输入了一行数据以后, ...

  9. 每秒生成一千万个【可视有序】分布式ID的简单方案

    去年做了一个产品,会经常导入导出大量的外部数据,这些数据的ID有的是GUID类型,有的是字符串,也有的是自增.GUID类型没有顺序,结果要排序得借助其它业务字段,整体查询效率比较低:字符串ID本来是用 ...

  10. LEDAPS1.3.0版本移植到windows平台----HuSr大气校正模块

    这个是2012年左右放在百度空间的,谁知百度空间关闭...转移到博客园. 最近项目用到3.1.2版本的LEDAPS,新版本的使用情况会在后续文章中慢慢丰富. HuSr是将LEDAPS项目中的TM/ET ...