Java原子操作保证方案
引言
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么做,但并不保证)。
三种保证方式
- 加锁
- AtomicXXX
- LongAdder
现在使用1000个线程,对一个数进行加1操作,每一个线程操作10_0000遍;
最终结果应该是:10_0000*1000 = 1_0000_0000
不作任何操作
在多线程的情况下,对一个变量进行修改,如果不做任何操作,使用传统的方式来进行的话,会导致最终的结果不是我们想要的结果。
int count = 0;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
UnsaveTest unsaveTest = new UnsaveTest();
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
int j = 0;
//每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
//所以 最终结果应该是:10_0000*1000 = 1_0000_0000
while (j++ < 10_0000) {
unsaveTest.count++;
}
});
}
long timeStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
for (int i = 0; i < 1000; i++) {
threads[i].join();
}
long timeEnd = System.currentTimeMillis();
System.out.println("期望结果:"+100000 * 1000);
System.out.println("运行结果 : " + unsaveTest.count);
System.out.println("耗时:" + (timeEnd - timeStart));
}
运行结果
期望结果:100000000
运行结果 : 97416060
耗时:82
加锁Synchronized
通过加锁的方式来保证原子操作,但有的时候同步变量只有一个,如果依然使用加锁的方式来保证原子性,在多线程竞争激烈的情况下,升级为重量级锁,会导致效率低下。
代码如下
int count = 0;
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
UnsaveTest unsaveTest = new UnsaveTest();
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
int j = 0;
//每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
//所以 最终结果应该是:10_0000*1000 = 1_0000_0000
while (j++ < 10_0000) {
synchronized (unsaveTest) {
unsaveTest.count++;
}
}
});
}
long timeStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
for (int i = 0; i < 1000; i++) {
threads[i].join();
}
long timeEnd = System.currentTimeMillis();
System.out.println("期望结果:" + 100000 * 1000);
System.out.println("运行结果 : " + unsaveTest.count);
System.out.println("耗时:" + (timeEnd - timeStart));
}
运行结果
期望结果:100000000
运行结果 : 100000000
耗时:2574
使用AtomicXXXX
AtomicXXXX是并发包里面提供的一系列并发工具类,通过Unsafe的CAS操作(最终调用是通过CPU指令级别的方式)来保证原子操作,也就是所谓的无锁优化
代码如下
AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
UnsaveTest unsaveTest = new UnsaveTest();
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
int j = 0;
//每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
//所以 最终结果应该是:10_0000*1000 = 1_0000_0000
while (j++ < 10_0000) {
unsaveTest.counter.incrementAndGet();
}
});
}
long timeStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
for (int i = 0; i < 1000; i++) {
threads[i].join();
}
long timeEnd = System.currentTimeMillis();
System.out.println("期望结果:" + 100000 * 1000);
System.out.println("运行结果 : " + unsaveTest.counter.get());
System.out.println("耗时:" + (timeEnd - timeStart));
}
运行结果
期望结果:100000000
运行结果 : 100000000
耗时:1006
使用LongAdder
LongAdder是JDK1.8后为了提高AtomicLong运行效率的一个新并发工具类
代码如下:
LongAdder longAdder = new LongAdder();
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
UnsaveTest unsaveTest = new UnsaveTest();
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
int j = 0;
//每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
//所以 最终结果应该是:10_0000*1000 = 1_0000_0000
while (j++ < 10_0000) {
unsaveTest.longAdder.increment();
}
});
}
long timeStart = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
for (int i = 0; i < 1000; i++) {
threads[i].join();
}
long timeEnd = System.currentTimeMillis();
System.out.println("期望结果:" + 100000 * 1000);
System.out.println("运行结果 : " + unsaveTest.longAdder.intValue());
System.out.println("耗时:" + (timeEnd - timeStart));
}
运行结果
期望结果:100000000
运行结果 : 100000000
耗时:212
总结
通过以上的几种测试,我们可以得出以下结果
在不作任何其它操作的情况下,运行效率最高,但是结果却不正确,这也是最大的问题。如果一个操作不能保证结果正确,那它就失去了意义。
在保证结果正确的情况下
Synchronized效率最低
Synchronized在多线程竞争激烈的情况下,效率会显著下降,因为无锁————》偏向锁————》轻量级锁(自旋锁)————》重量级锁,大量线程在多次等待锁的过程中,会自旋为重量级锁,这个时候效率会变得很低。
其次是AtomicInteger
AtomicInteger底层使用的Unsafe类的CAS操作,是通过CPU指令级别的方式来保证原子操作。效率也还可以。
效率最高的是LongAdder。
LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作,取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同
Java原子操作保证方案的更多相关文章
- Java架构师方案—多数据源开发详解及原理(二)(附完整项目代码)
1. mybatis下数据源开发工作 2. 数据源与DAO的关系原理模型 3. 为什么要配置SqlSessionTemplate类的bean 4. 多数据源应用测试 1. mybatis下数据源开发工 ...
- Java 并发系列之九:java 原子操作类Atomic(13个)
1. 原子更新基本类型类 2. 原子更新数组 3. 原子更新引用 4. 原子更新属性 5. txt java 原子操作类Atomic 概述 java.util.concurrent.atomic里的原 ...
- 项目四:Java秒杀系统方案优化-高性能高并发实战
技术栈 前端:Thymeleaf.Bootstrap.JQuery 后端:SpringBoot.JSR303.MyBatis 中间件:RabbitMQ.Redis.Druid 功能模块 分布式会话,商 ...
- Java如何保证文件落盘?
本文转载自Java如何保证文件落盘? 导语 在之前的文章Linux/UNIX编程如何保证文件落盘中,我们聊了从应用到操作系统,我们要如何保证文件落盘,来确保掉电等故障不会导致数据丢失.JDK也封装了对 ...
- java原子操作
一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中 ...
- java原子操作的实现原理--转载
原文地址:http://www.infoq.com/cn/articles/atomic-operation 1. 引言 原子(atom)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic ...
- Java原子操作类AtomicInteger应用场景
Java中有那么一些类,是以Atomic开头的.这一系列的类我们称之为原子操作类.以最简单的类AtomicInteger为例.它相当于一个int变量,我们执行Int的 i++ 的时候并不是一个原子操作 ...
- Java原子操作类汇总
当程序更新一个变量时,如果是多线程同时更新这个变量,可能得到的结果与期望值不同.比如:有一个变量i,A线程执行i+1,B线程也执行i+1,经过两个线程的操作后,变量i的值可能不是期望的3,而是2.这是 ...
- Java原子操作类,你知道多少?
原子操作类简介 由于synchronized是采用的是悲观锁策略,并不是特别高效的一种解决方案. 实际上,在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去 更新基 ...
- 面试必备:Java 原子操作的实现原理[精品长文]
本文整理自<Java并发编程的艺术>第二章 作者:方腾飞 魏鹏 程晓明 原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为“不可被 ...
随机推荐
- Serverless 架构落地实践及案例解析
简介: 技术演进的本质是更好服务业务,传统开发方式使企业花费更多的精力打磨底层技术细节,而 Serverless 架构就是让开发者专注业务实现从而创造更大的业务价值. 作者 | 丹坤 整理 | 徐 ...
- Alibaba/IOC-golang 正式开源 ——打造服务于go开发者的IOC框架
简介: IOC(inversion of control)即控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度.IOC-golang 是一款服务于Go语言开发者的依赖注入框架 ...
- OpenYurt 深度解读:如何构建 Kubernetes 原生云边高效协同网络?
作者 | 郑超 导读:OpenYurt 是阿里巴巴开源的云边协同一体化架构,与同类开源方案相比,OpenYurt 拥有可实现边缘计算全场景覆盖的能力.在之前的一篇文章中,我们介绍了 OpenYurt ...
- 基于 EventBridge 构建 SaaS 应用集成方案
简介:事件源是事件驱动的基石,如何获取更多事件源也是 EventBridge 一直在探索和尝试的方向.针对市场上其他云厂商和垂直领域的 Saas 服务,EventBridge 发布了 HTTP So ...
- 【实践案例】Databricks 数据洞察 Delta Lake 在基智科技(STEPONE)的应用实践
简介: 获取更详细的 Databricks 数据洞察相关信息,可至产品详情页查看:https://www.aliyun.com/product/bigdata/spark 作者 高爽,基智科技数据中心 ...
- 图像检索在高德地图POI数据生产中的应用
简介: 高德通过自有海量的图像源,来保证现实世界的每一个新增的POI及时制作成数据.在较短时间间隔内(小于月度),同一个地方的POI 的变化量是很低的. 作者 | 灵笼.怀迩 来源 | 阿里技术 ...
- Apache Flink 在京东的实践与优化
简介: Flink 助力京东实时计算平台朝着批流一体的方向演进. 本文整理自京东高级技术专家付海涛在 Flink Forward Asia 2020 分享的议题<Apache Flink 在京 ...
- 2018-8-10-WPF-如何画出1像素的线
title author date CreateTime categories WPF 如何画出1像素的线 lindexi 2018-08-10 19:16:53 +0800 2018-2-13 17 ...
- k8s-1.28版本多master部署
一.环境准备 k8s集群角色 IP 主机名 安装相关组件 kubernetes版本号 控制节点 192.168.10.20 master apiserver.controller-manager.sc ...
- K8s控制器---Replicaset(7)
一.Replicaset(目前少用了) 1.1 控制器管理pod 什么是控制器?前面我们学习了 Pod,那我们在定义 pod 资源时,可以直接创建一个 kind:Pod 类型的自主式 pod,但是这存 ...