高并下如何做变量的自增与自减
变量的自增与自减
变量的自增自减相信大家都会,一般情况下直接++
或--
就可以了。但是实际情况我们可能需要考虑并发问题,多线程情况下,如果我们直接计算。计算结果可能就会不准确。
public static int num = 0;public static void increase() {num++;
}public static void main(String[] args) throws InterruptedException {Thread[] threads = new Thread[10];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 1000; j++) {//increaseWithLock();increase();}});threads[i].start();}for (Thread thread : threads) {thread.join();}System.out.println(num);
}
我们直接这么操作,结果就会不准确。上述代码运算结果为:
9589
并不是我们预算的10000。
2加锁的自增与自减
这时我们就会给运算方法加锁,synchronized
或者lock
都行
public static synchronized void increaseWithSync() {num++;
}//或者
public static void increaseWithLock() {try {lock.lock();num++;} finally {lock.unlock();}
}
运行结果:
10000
但是用到了锁,这个东西可以说偏重量级的了,会引起线程上下文的切换和调用,线程之间的切换也会有性能成本的。这是我们就要使用JDK自带的原子类了。
3原子自增与自减
我们来看看java.util.concurrent.atomic
包下面的原子类AtomicInteger
。看下面的代码:
AtomicInteger atomicInteger = new AtomicInteger();@Test
public void test() throws InterruptedException {Thread[] threads = new Thread[10];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 1000; j++) {num = atomicInteger.incrementAndGet();}});threads[i].start();}for (Thread thread : threads) {thread.join();}System.out.println(num);
}
代码运行结果:
10000
符合预期。
Java的原子类主要采用CAS + 自旋实现,但是在高并发情况下,还是存在一些性能问题的:
高并发量的情况下,由于真正更新成功的线程占少数,容易导致循环次数过多,浪费时间,并且浪费线程资源。
由于需要保证变量真正的共享,**「缓存一致性」**开销变大。
之前我写了一篇关于如何手写Atomic原子类的文章,有兴趣的同学可以看看:
没用过Java原子类?我来手写一个
AtomicInteger
实际上Java还提供了性能更优越的LongAdder
。我们来看看LongAdder
怎么使用。
private static volatile LongAdder longAdder = new LongAdder();@Test
public void test() throws InterruptedException {Thread[] threads = new Thread[10];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(() -> {for (int j = 0; j < 1000; j++) {//num = atomicInteger.incrementAndGet();longAdder.increment();}});threads[i].start();}for (Thread thread : threads) {thread.join();}System.out.println(longAdder);
}
运行结果同样符合预期
10000
那么LongAdder
性能为什么高呢?
Benchmark Mode Cnt Score Error Units
AtomicTest.atomicLongAdd thrpt 200 52860.651 ± 1337.731 ops/ms
AtomicTest.longAdderAdd thrpt 200 486609.475 ± 5204.630 ops/ms
采用JMH做Benchmark基准测试,分别使用10个线程测试两个方法的吞吐量。测试的性能结果如上。我们发现LongAdder
吞吐量明显要高。
唯一会制约AtomicXXX
高效的原因是高并发,高并发意味着CAS的失败几率更高, 重试次数更多,越多线程重试,CAS失败几率又越高,变成恶性循环,AtomicXXX
效率降低。那怎么解决?
LongAdder
的解决方案是:减少并发,将单一value的更新压力分担到多个value中去,降低单个value的 “热度”,分段更新。这样,线程数再多也会分担到多个value上去更新,只需要增加value就可以降低 value的 “热度” 。
简而言之,LongAdder
采用空间换时间。
4分布式系统中的自增与自减
我们来看这样一个需求:
用户注册就会给用户分配一个编号,编号规则按用户先后注册顺序递增,比如第一位注册的用户编号为100,第二位就为101,依次类推。
这里我们就要考虑并发,不能创建重复的编号。你可能会说,这个简单,我就用上面的LongAdder
,性能好,线程安全,不会出现重复编号的情况。
但是实际上我们的系统可能有多个实列,上面的LongAdder
只是JVM级别的,在自己的实列中获取可以实现安全的自增。在有多个实例的系统中就不行了,为了实现上面的需求,我们可以使用数据库的特性来生成编号。
一般的数据库如MySQL可能会有性能问题。这里我推荐使用Redis
来生成。由于Redis
的主计算线程属于单线程,使用Redis
安全又高效。
Java有个Redis
的API RedissonClient
可以用来实现原子自增与自减。
首先我们需要创建一个RedissonClient
实例:
private RedissonClient getRedissonClient() {Config config = new Config();SingleServerConfig singleServerConfig = config.useSingleServer();singleServerConfig.setAddress("redis://127.0.0.1:6379");singleServerConfig.setPassword("lvshen");RedissonClient redissonClient = Redisson.create(config);return redissonClient;
}
然后我们就用这个实例做自增计算
public long getCode(String key) {RedissonClient redissonClient = getRedissonClient();RAtomicLong atomicVar = redissonClient.getAtomicLong(key);if (!atomicVar.isExists()) {atomicVar.set(100);}long value = atomicVar.incrementAndGet(); // 多线程调用该方法,不会造成数据丢失return value;
}
上面的代码就实现了在分布式系统中的原子自增。
以上就是今天的全部内容啦,如果对你有用,欢迎点赞+转发。
高并下如何做变量的自增与自减相关推荐
- ssis什么情况下用到变量_了解SSIS内存使用情况
ssis什么情况下用到变量 In this article, I am going to explain in detail about SSIS memory usage and how can w ...
- 卜东波研究员:高观点下的少儿计算思维
2018年的一天,我给同事包云岗打了个电话,说:"孩子们上二年级了,能否在轻松.活泼的氛围下,引导孩子们学一些数学思维呢?"云岗很赞同,我们就一起组织了一个小SIGMA数学特别兴趣 ...
- 20165315 第八周考试课下补做
20165315 第八周考试课下补做 测试-2-ch03 1.通过输入gcc -S -o main.s main.c 将下面c程序"week0303学号.c"编译成汇编代码 int ...
- Linux下Java环境变量配置
在Java开发中,在安装完jdk之后,首先需要做的工作就是进行Java环境变量配置.在Windows下的配置我们都比较熟悉,图形化界面配置起来也相对容易(详见:Windows7下环境变量配置),接下来 ...
- 苹果电脑上使用linux环境变量,mac系统下修改环境变量
苹果电脑使用率越来越高,在mac系统下研发,性能要比在windows下快不少,既然要开发,免不了要配置环境变量.下面是学习啦小编收集整理的mac系统下修改环境变量,希望对大家有帮助~~ mac系统下修 ...
- 成功解决ThinkPad T14 高负载下CPU降频问题
本人机器是ThinkPad T14 , CPU是 Intel i5-10210U,随着气温的升高,发现机器越来越卡,特别是在运行大型软件和处理大型文档的时候,看了下任务管理器,高负载时CPU不仅没有睿 ...
- Java 不允许在一个范围大的作用域下重新定义同名变量
Java中不允许在一个范围大的作用域下重新定义同名变量. {inx x = 1;{int x = 2;} } 上面的代码虽然在C和C++里合法(将一个较大作用域的变量"隐藏"),但 ...
- 量化交易下怎么做波段?
量化交易下怎么做波段,今天小编就跟大家来总结一下,关于量化波段操作有哪些技巧. 量化交易波段操作的要点和技巧是:高卖低吸.在普通股调整区,投资者根据近期股市的涨跌发现了波谷和波峰,并进行了波段操作.当 ...
- 夫兵形象水,水之行避高趋下,兵之形避实击虚;水因地制流,兵因故制胜。故兵无常势水无常形。能因敌变化取胜者谓之神。
因敌变化取胜者谓之神 OSW 2006-04-23 阅读次数:27 孙子兵法有云:夫兵形象水,水之行避高趋下,兵之形避实击虚:水因地制流,兵因故制胜.故兵无常势水无常形.能因敌变化取胜者谓之神. 市场 ...
最新文章
- 剑指offer十一:二进制中1的个数
- 线段树的数组大小下限及证明
- *【CodeForces - 195B】After Training (多解,模拟)
- layui单选框verify_layui 单选框选中事件
- 分布式监控系统Zabbix-3.0.3-完整安装记录(4)-解决zabbix监控图中出现中文乱码问题...
- Python——KNN实战(附详细代码与注解)
- idea 光标 快捷键_IntelliJ Idea 常用快捷键
- 苹果Mac文件夹样式设计工具:Folder Designer
- 线性表的总结:顺序存储线性表的初始化,创建,插入,删除,清空,销毁等操作...
- 奥迪坚受邀参加银联数据2016年度客服云平台专题研讨会
- 读《怎样解题:数学思维的新方法》有感
- 计算机等级考试如何评改试题,全国计算机考试上机考试是如何改卷的
- 如何获取大数据行业高薪岗位offer?
- 遗传算法java(中国外运杯)
- JavaBean与Map互转
- java2 day03 XML DOM4J
- 如何高效的读透一本书?我读了100本书总结出的3个阅读技巧
- 激光SLAM理论与实践(一)--激光SLAM简要介绍
- 抖音作品和直播间如何获取更多流量
- 企业信息安全————2、如何描述风险价值
热门文章
- Python Flask基本使用以及与Pytorch整合
- android dropbear 密码,[Android]dropbear on android
- 计算机二级ms office选择题怎么学,全国计算机二级MS Office考试注意事项
- React教程(二)——jsx语法、条件渲染、列表渲染
- 清明扫墓一定要知道的常识
- 基于HTML5的移动Web应用——Bootstrap 样式案例:制作微票儿首页
- 球球大作战显示中国服务器较差,球球大作战新版本常见使用问题的解决方案
- Android 使用Stetho在Chrome浏览器查看SQLite数据库
- 天蚕土豆《大主宰》场景复现
- python中最小公倍数函数_Python实现的求解最小公倍数算法示例