原子操作之 DoubleAdder
1. 前言
今天为大家介绍原子操作之 DoubleAdder。此工具位于 java.util.concurrent.atomic 包中。
本节先介绍 DoubleAdder 工具类的基本概念和最基本用法,之后给出 DoubleAdder 工具类最常用的场合说明,然后通过简单的编码实现一个实际案例,让大家有一个理性的认识,最后带领大家熟悉 DoubleAdder 最常用的一些编程方法,进一步加深对 DoubleAdder 工具类的理解。
下面我们正式开始介绍吧。
2. 概念介绍
DoubleAdder 工具类采用了 “分头计算最后汇总” 的思路,避免每一次(细粒度)操作的并发控制,提高了并发的性能。什么是细粒度的同步控制呢?所谓细粒度的同步控制,指的是对待同步控制对象的每一次操作都需要加以控制,这样描述是不是有点抽象,别着急,看下面的图示。
我们看下面 DoubleAdder 工具类的基本用法。
3. 基本用法
// 首先创建一个 DoubleAdder 对象
DoubleAdder doubleAdder = new DoubleAdder();
...
// 调用累加方法
doubleAdder.add(50.5);
doubleAdder.add(49.5);
// 调用求和方法
double sum = doubleAdder.sum();
...
是不是很简单,那 DoubleAdder 在我们日常实践中,到底应该应用在哪些场合比较合适呢?下面我们给出最常用的场景说明。
4. 常用场景
DoubleAdder 经常用于多线程并发做收集统计数据的场合,而不是细粒度的同步控制。
下面我们用 DoubleAdder 工具类实现一个生活案例:某商场为了掌握客流特征,在商场所有出入口架设了人体特征识别设备,此类设备可以有效识别客人性别等信息。基于此,商场管理办公室计划制作一个客流性别流量图表,用于决策商场的服务内容。
5. 场景案例
import java.util.concurrent.atomic.DoubleAdder;
public class DoubleAdderTest {
// 首先创建三个 DoubleAdder 对象分别表示统计结果
// 代表当天所有进入商场的男性客户总数量
private static DoubleAdder maleCount = new DoubleAdder();
// 代表当天所有进入商场的女性客户总数量
private static DoubleAdder womenCount = new DoubleAdder();
// 代表当天所有进入商场的未能识别的客户总数量
private static DoubleAdder unknownGenderCount = new DoubleAdder();
public static void main(String[] args) {
// 定义30个商场入口检测设备
for (int i = 1; i <= 30; i++) {
MonitoringDevice monitoringDevice = new MonitoringDevice(maleCount, womenCount, unknownGenderCount, i);
// 开启检测设备进行检测
new Thread(monitoringDevice).start();
}
}
}
在上面的代码中,首先创建三个 DoubleAdder 对象分别表示统计结果,然后创建了 30 个商场入口检测设备模拟检测识别,接下来每个检测设备如何动作呢,看下面的代码。
import java.util.Random;
import java.util.concurrent.atomic.DoubleAdder;
public class MonitoringDevice implements Runnable {
private DoubleAdder maleCount;
private DoubleAdder womenCount;
private DoubleAdder unknownGenderCount;
private String monitoringDeviceNo;
public MonitoringDevice(DoubleAdder maleCount, DoubleAdder womenCount, DoubleAdder unknownGenderCount, int monitoringDeviceNo) {
this.maleCount = maleCount;
this.womenCount = womenCount;
this.unknownGenderCount = unknownGenderCount;
this.monitoringDeviceNo = "第" + monitoringDeviceNo + "监控采集处";
}
public void run() {
while (true) {
// 监测处理 (监测设备输出1代表男性,0代表女性,其他代表未能识别,此处随机产生监测结果)
try {
Thread.sleep(new Random().nextInt(3000));
} catch (Exception e) {}
int monitoringDeviceOutput = new Random().nextInt(3);
// 对监测结果进行统计
switch (monitoringDeviceOutput) {
case 0: womenCount.add(1);
System.out.println("统计结果: womenCount=" + womenCount.sum());
break;
case 1: maleCount.add(1);
System.out.println("统计结果: maleCount=" + maleCount.sum());
break;
default: unknownGenderCount.add(1);
System.out.println("统计结果: unknownGenderCount=" + unknownGenderCount.sum());
break;
}
}
}
}
在 MonitoringDevice 类中,首先模拟监测设备输出,然后将输出结果使用 add () 进行统计累加,使用 sum () 输出累加结果。运行一段时间后运行结果如下。
...
统计结果: unknownGenderCount=23.0
统计结果: womenCount=24.0
统计结果: maleCount=32.0
...
上面的案例中,总共计算了三个统计值,每一个统计值都使用了多个线程同时进行统计计算。在统计过程中,每一个线程只需要累加自己的那份统计结果,所以不需要做同步控制,只要在最后进行汇总统计结果时做同步控制进行汇总即可。像这样的场景使用 DoubleAdder 工具类会非常方便简洁。
至此,大家对 DoubleAdder 已经有了初步的理解,接下来我们继续丰富对 DoubleAdder 工具类的认识。
6. 核心方法介绍
除过上面代码中使用的最基本的 add (int)、sum () 方法之外,我们再介绍两个方法的使用。
- reset () 方法
将累加器值置为 0,即为后继使用重新归位。
- sumThenReset () 方法
此方法逻辑等同于先调用 sum () 方法再调用 reset () 方法,简化代码编写。
7. 小结
本节通过一个简单的例子,介绍了 DoubleAdder 的基本用法。在 java.util.concurrent.atomic 包中还有一个类似的工具类 LongAdder,用法大同小异,希望大家在日常研发中多比较多总结,早日掌握之。