变量的自增与自减

变量的自增自减相信大家都会,一般情况下直接++--就可以了。但是实际情况我们可能需要考虑并发问题,多线程情况下,如果我们直接计算。计算结果可能就会不准确。

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;
}

上面的代码就实现了在分布式系统中的原子自增。

以上就是今天的全部内容啦,如果对你有用,欢迎点赞+转发。

高并下如何做变量的自增与自减相关推荐

  1. ssis什么情况下用到变量_了解SSIS内存使用情况

    ssis什么情况下用到变量 In this article, I am going to explain in detail about SSIS memory usage and how can w ...

  2. ​卜东波研究员:高观点下的少儿计算思维

    2018年的一天,我给同事包云岗打了个电话,说:"孩子们上二年级了,能否在轻松.活泼的氛围下,引导孩子们学一些数学思维呢?"云岗很赞同,我们就一起组织了一个小SIGMA数学特别兴趣 ...

  3. 20165315 第八周考试课下补做

    20165315 第八周考试课下补做 测试-2-ch03 1.通过输入gcc -S -o main.s main.c 将下面c程序"week0303学号.c"编译成汇编代码 int ...

  4. Linux下Java环境变量配置

    在Java开发中,在安装完jdk之后,首先需要做的工作就是进行Java环境变量配置.在Windows下的配置我们都比较熟悉,图形化界面配置起来也相对容易(详见:Windows7下环境变量配置),接下来 ...

  5. 苹果电脑上使用linux环境变量,mac系统下修改环境变量

    苹果电脑使用率越来越高,在mac系统下研发,性能要比在windows下快不少,既然要开发,免不了要配置环境变量.下面是学习啦小编收集整理的mac系统下修改环境变量,希望对大家有帮助~~ mac系统下修 ...

  6. 成功解决ThinkPad T14 高负载下CPU降频问题

    本人机器是ThinkPad T14 , CPU是 Intel i5-10210U,随着气温的升高,发现机器越来越卡,特别是在运行大型软件和处理大型文档的时候,看了下任务管理器,高负载时CPU不仅没有睿 ...

  7. Java 不允许在一个范围大的作用域下重新定义同名变量

    Java中不允许在一个范围大的作用域下重新定义同名变量. {inx x = 1;{int x = 2;} } 上面的代码虽然在C和C++里合法(将一个较大作用域的变量"隐藏"),但 ...

  8. 量化交易下怎么做波段?

    量化交易下怎么做波段,今天小编就跟大家来总结一下,关于量化波段操作有哪些技巧. 量化交易波段操作的要点和技巧是:高卖低吸.在普通股调整区,投资者根据近期股市的涨跌发现了波谷和波峰,并进行了波段操作.当 ...

  9. 夫兵形象水,水之行避高趋下,兵之形避实击虚;水因地制流,兵因故制胜。故兵无常势水无常形。能因敌变化取胜者谓之神。

    因敌变化取胜者谓之神 OSW 2006-04-23 阅读次数:27 孙子兵法有云:夫兵形象水,水之行避高趋下,兵之形避实击虚:水因地制流,兵因故制胜.故兵无常势水无常形.能因敌变化取胜者谓之神. 市场 ...

最新文章

  1. 剑指offer十一:二进制中1的个数
  2. 线段树的数组大小下限及证明
  3. *【CodeForces - 195B】After Training (多解,模拟)
  4. layui单选框verify_layui 单选框选中事件
  5. 分布式监控系统Zabbix-3.0.3-完整安装记录(4)-解决zabbix监控图中出现中文乱码问题...
  6. Python——KNN实战(附详细代码与注解)
  7. idea 光标 快捷键_IntelliJ Idea 常用快捷键
  8. 苹果Mac文件夹样式设计工具:Folder Designer
  9. 线性表的总结:顺序存储线性表的初始化,创建,插入,删除,清空,销毁等操作...
  10. 奥迪坚受邀参加银联数据2016年度客服云平台专题研讨会
  11. 读《怎样解题:数学思维的新方法》有感
  12. 计算机等级考试如何评改试题,全国计算机考试上机考试是如何改卷的
  13. 如何获取大数据行业高薪岗位offer?
  14. 遗传算法java(中国外运杯)
  15. JavaBean与Map互转
  16. java2 day03 XML DOM4J
  17. 如何高效的读透一本书?我读了100本书总结出的3个阅读技巧
  18. 激光SLAM理论与实践(一)--激光SLAM简要介绍
  19. 抖音作品和直播间如何获取更多流量
  20. 企业信息安全————2、如何描述风险价值

热门文章

  1. Python Flask基本使用以及与Pytorch整合
  2. android dropbear 密码,[Android]dropbear on android
  3. 计算机二级ms office选择题怎么学,全国计算机二级MS Office考试注意事项
  4. React教程(二)——jsx语法、条件渲染、列表渲染
  5. 清明扫墓一定要知道的常识
  6. 基于HTML5的移动Web应用——Bootstrap 样式案例:制作微票儿首页
  7. 球球大作战显示中国服务器较差,球球大作战新版本常见使用问题的解决方案
  8. Android 使用Stetho在Chrome浏览器查看SQLite数据库
  9. 天蚕土豆《大主宰》场景复现
  10. python中最小公倍数函数_Python实现的求解最小公倍数算法示例