Java的Atomic原子类
Java SDK 并发包里提供了丰富的原子类,我们可以将其分为五个类别,这五个类别提供的方法基本上是相似的,并且每个类别都有若干原子类。
- 对基本数据类型的变量值进行原子更新;
- 对对象变量的指向进行原子更新;
- 对数组里面的的元素进行原子更新;
- 原子化的对象属性更新器;
- 原子化的累加器。
基本数据类型
AtomicBoolean、AtomicLong、AtomicInteger 这三个类提供了一些对基本数据类型的变量值进行原子更新的方法。
这些类提供的方法是相似的,主要有(以 AtomicLong 为例):
// 原子化的 i++
long getAndIncrement()
// 原子化的 i--
long getAndDecrement()
// 原子化的 ++i
long incrementAndGet()
// 原子化的 --i
long decrementAndGet()
// 原子化的 i+=delta,返回值为+=前的i值
long getAndAdd(long delta)
// 原子化的 i+=delta,返回值为+=后的i值
long addAndGet(delta)
// CAS操作。如果写回成功返回true,否则返回false
boolean compareAndSet(long expect, long update)
// 以下四个方法新值可以通过传入函数式接口(func函数)来计算
long getAndUpdate(LongUnaryOperator updateFunction)
long updateAndGet(LongUnaryOperator updateFunction)
long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction)
long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction)
// 演示 getAndUpdate() 方法的使用
public static void main(String[] args) {
AtomicLong atomicLong = new AtomicLong(0);
long result = atomicLong.getAndUpdate(new LongUnaryOperator() {
@Override
public long applyAsLong(long operand) {
return operand + 1;
}
});
}
对象引用类型
AtomicReference、AtomicStampedReference、AtomicMarkableReference 这三个类提供了一些对对象变量的指向进行原子更新的方法。如果需要对对象的属性进行原子更像,那么可以使用原子化的对象属性更新器。
public class ClassName {
AtomicReference<Employee> employeeAR = new AtomicReference<>(new Employee("小明"));
public void methodName() {
Employee oldVal = employeeAR.get();
Employee newVal = new Employee(oldVal.getName());
employeeAR.compareAndSet(oldVal, newVal);
}
}
对象引用的原子化更新需要重点关注 ABA 问题。当一个线程在进行 CAS 操作时,另一个线程可能会在此期间修改了同一个共享变量的值,然后又将其改回原来的值。这种情况下,CAS 操作就无法检测到共享变量值的变化,从而导致 ABA 问题。如果我们仅仅在写回数据前判断数值是 A,可能导致不合理的写回操作。AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。
- AtomicStampedReference 通过为对象引用建立类似版本号(stamp)的方式,来解决 ABA 问题。AtomicStampedReference 实现的 CAS 方法增加了版本号参数
- AtomicMarkableReference 的实现机制则更简单,将版本号简化成了一个 Boolean 值
boolean compareAndSet(V expectedReference, V newReference,
int expectedStamp, int newStamp)
boolean compareAndSet(V expectedReference, V newReference,
boolean expectedMark, boolean newMark)
数组
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray 这三个类提供了一些对数组里面的的元素进行原子更新的方法。
public class ClassName {
AtomicLongArray atomicLongArray = new AtomicLongArray(new long[]{0, 1});
public void methodName() {
int index = 0;
long oldVal = atomicLongArray.get(index);
long newVal = oldVal + 1;
atomicLongArray.compareAndSet(index, oldVal, newVal);
}
}
原子化的对象属性更新器
原子化的对象属性更新器有:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。
这三个类提供了一些对对象的属性进行原子更新的方法。这些方法是利用反射机制实现的。
public class ClassName {
AtomicIntegerFieldUpdater<Employee> fieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(Employee.class, "salary");
Employee employee = new Employee("小明", 1000);
public void methodName() {
int oldVal = employee.getSalary();
int newVal = oldVal + 1000;
fieldUpdater.compareAndSet(employee, oldVal, newVal);
}
}
需要注意的是:
- 对象属性的类型必须是基本数据类型,不能是基本数据类型对应的包装类。如果对象属性的类型不是基本数据类型,newUpdater() 方法会抛出 IllegalArgumentException 运行时异常。
- 对象的属性必须是 volatile 类型的,只有这样才能保证可见性。如果对象的属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 运行时异常。
// AtomicIntegerFieldUpdater 类中的代码
if (field.getType() != int.class) {
throw new IllegalArgumentException("Must be integer type");
}
if (!Modifier.isVolatile(modifiers)) {
throw new IllegalArgumentException("Must be volatile type");
}
原子化的累加器
原子化的累加器有:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。这四个类仅仅用来在多线程环境下,执行累加操作。
相比原子化的基本数据类型,原子化的累加器的速度更快,但是它(原子化的累加器)不支持 compareAndSet() 方法。如果仅仅需要累加操作,使用原子化的累加器性能会更好。
原子化的累加器的本质是空间换时间。
LongAdder 的使用示例如下所示:
public static void main(String[] args) {
LongAdder adder = new LongAdder();
// 初始化
adder.add(1);
// 累加
for (int i = 0; i < 100; i++) {
adder.increment();
}
long sum = adder.sum();
}
LongAccumulator 与 LongAdder 类似,但 LongAccumulator 提供了更加灵活的累加操作,可以自定义累加函数。
使用示例如下所示。在使用示例中,我们创建了一个 LongAccumulator 对象,初始值为1,累加函数为 (x, y) -> x * y,即每次累加都将之前的结果与新的值相乘。然后,我们累加了三个数值,最后输出累加结果。由于累加函数是(x, y) -> x * y,所以最终的累加结果为1 * 5 * 10 * 20 = 1000。
public static void main(String[] args) {
LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left * right;
}
}, 1);
// 初始值为1,累加函数为(x, y) -> x * y
accumulator.accumulate(5);
accumulator.accumulate(10);
accumulator.accumulate(20);
// 累加结果为 1 * 5 * 10 * 20 = 1000
long result = accumulator.get();
}
参考资料
21 | 原子类:无锁工具类的典范 (geekbang.org)
Java的Atomic原子类的更多相关文章
- Java线程--Atomic原子类使用
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871241.html Java线程--Atomic原子类使用 package concurr ...
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ...
- (转)Java atomic原子类的使用方法和原理(一)
在讲atomic原子类之前先看一个小例子: public class UseAtomic { public static void main(String[] args) { AtomicIntege ...
- Java多线程系列——原子类的实现(CAS算法)
1.什么是CAS? CAS:Compare and Swap,即比较再交换. jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronou ...
- JUC 中的 Atomic 原子类总结
1 Atomic 原子类介绍 Atomic 翻译成中文是原子的意思.在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的.在我们这里 Atomic 是指一个操作是不可中断的.即使是 ...
- Atomic原子类
Atomic原子类 Atomic原子类位于并发包java.util.concurrent下的java.util.concurrent.Atomic中. 1. 原子更新基本类型类 使用原子方式更新基本数 ...
- Juc中Atomic原子类总结
1 Atomic原子类介绍 2 基本类型原子类 3 数组类型原子类 4 引用类型原子类 5 对象的属性修改类型原子类
- [Java多线程]-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...
- 深入浅出Java并发包—原子类操作
我们知道,JDK1.5以后引入了并发包(java.util.concurrent)用于解决多CPU时代的并发问题,而并发包中的类大部分是基于Queue的并发类,Queue在大多数情况下使用了原子类(A ...
随机推荐
- 阿里云镜像创建Spring Boot工厂
参考博客:https://blog.csdn.net/qq_40052237/article/details/115794368 http://start.aliyun.com
- java多线程--6 死锁问题 锁Lock
java多线程--6 死锁问题 锁Lock 死锁问题 多个线程互相抱着对方需要的资源,然后形成僵持 死锁状态 package com.ssl.demo05; public class DeadLock ...
- java多线程--3 线程状态、线程方法、线程类型
java多线程--3 线程状态.线程方法.线程类型 线程状态 创建状态: **就绪状态: ** 进入状态: 创建状态:启动线程 阻塞状态:阻塞解除 运行状态:释放CPU资源 阻塞状态: 进入状态: 运 ...
- Tomcat启动JSP项目,搞起来了
虽然有点复古,但是还是有很多小伙伴在使用的,小编来一篇保姆级教程 1.用idea打开jsp项目 2.添加tomcat配置 3.点击后会出现配置框,这里画框的地方都选上,版本选择1.8,其他的信息内容默 ...
- JS中关于原型对象与原型链的理解!
1.首先我们先来看一张图 prototype 每个函数都有一个 prototype 属性每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每 ...
- InnoDB Buffer Pool改进LRU页面置换
由于硬盘和内存的造价差异,一台主机实例的硬盘容量通常会远超于内存容量.对于数据库等应用而言,为了保证更快的查询效率,通常会将使用过的数据放在内存中进行加速读取. 数据页与索引页的LRU 数据页和索引页 ...
- Teamcenter_SOA开发:使用SOA登录Teamcenter
本文Teamcenter SOA使用C++参考SOA的例子进行编写,以下代码为登录Teamcenter,代码工程在Teamcenter四层环境下运行. SOA的库文件.样例文件.帮助文件在Teamce ...
- LeeCode 942 增减字符串匹配
LeeCode 942 题目描述: 由范围 [0,n] 内所有整数组成的 n+1 个整数的排列序列可以表示为长度为 n 的字符串 s ,其中: 如果 perm[i] < perm[i + 1] ...
- mysql中innodb_open_files限制导致数据库异常重启
问题描述:收到监控软件告警,提示数据库发生重启,进去查看,截止到6/27 10:00 之前,作为主节点的orch1先重启,然后故障转移到orch2和orch3节点上.在持续到6/27 9:00 左右, ...
- 使用vue-cli创建第一个vue项目
命令提示符切换至需要创建项目的目录: 直接在路径输入cmd在按键盘的enter键打开的终端就直接切换到该目录下 (1)输入以下命令: vue create 项目名称 (2)我这里选手动选择,键盘上下按 ...