我们认为,由于思维定式原子变量总是比同步运行的速度更快,我想是这样也已经,直到实现了ID在第一次测试过程生成器不具有在这样一个迷迷糊糊的东西。

测试代码:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;public class ConcurrentAdder {private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);private static int I = 0;private static final Object o = new Object();private static volatile long start;public static void main(final String[] args) {//每一个线程运行多少次累加int round = 10000000;//线程个个数int threadn = 20;start = System.currentTimeMillis();atomicAdder(threadn, round);//syncAdder(threadn, round);}static void atomicAdder(int threadn, int addTimes) {int stop = threadn * addTimes;List<Thread> list = new ArrayList<Thread>();for (int i = 0; i < threadn; i++) {list.add(startAtomic(addTimes, stop));}for (Thread each : list) {each.start();}}static Thread startAtomic(final int addTimes, final int stop) {Thread ret = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < addTimes; i++) {int v = ATOMIC_INTEGER.incrementAndGet();if (stop == v) {System.out.println("value:" + v);System.out.println("elapsed(ms):" + (System.currentTimeMillis() - start));System.exit(1);}}}});ret.setDaemon(false);return ret;}static void syncAdder(int threadn, int addTimes) {int stop = threadn * addTimes;List<Thread> list = new ArrayList<Thread>();for (int i = 0; i < threadn; i++) {list.add(startSync(addTimes, stop));}for (Thread each : list) {each.start();}}static Thread startSync(final int addTimes, final int stop) {Thread ret = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < addTimes; i++) {synchronized (o) {I++;if (stop == I) {System.out.println("value:" + I);System.out.println("elapsed(ms):" + (System.currentTimeMillis() - start));System.exit(1);}}}}});ret.setDaemon(false);return ret;}
}

这是一个非常easy的累加器,N个线程并发累加,每一个线程累加R次。

分别凝视

atomicAdder(threadn, round);//原子变量累加
syncAdder(threadn, round);//同步累加

中的一行运行还有一行

笔者机器的配置:i5-2520M 2.5G 四核

N=20

R=10000000

结果:

原子累加:15344 ms

同步累加:10647 ms

问题出来了,为什么同步累加会比原子累加要快50%左右?

@ 我们知道java加锁的过程是(内置sync和显式lock类似),要加锁的线程检查下锁是否被占用。假设被占用则增加到目标锁的等待队列。假设没有则。加锁。

这里我们每一个线程获取到锁累加之后就立刻又去获取锁,这时其它线程还没有被唤醒。锁又被当前线程拿到了。

这也就是非公平锁可能造成的饥饿问题。

可是这一个原因不能解释50%的性能提升?理论上。在一个绝对时间。总有一个线程累加成功,那么两种累加器的耗时应该近似才对。

那么是有什么提升了同步累加的性能。或者是什么减少了原子累加的性能?

@接下来笔者分别perf了一下两种累加器的运行过程:

第一次运行的是原子累加器,第二次运行的同步累加器。

wxf@pc:/data$ perf stat -e cs -e L1-dcache-load-misses java ConcurrentAdder
value:100000000
elapsed(ms):8580Performance counter stats for 'java ConcurrentAdder 1 100 1000000':21,841 cs                                                          233,140,754 L1-dcache-load-misses                                       8.633037253 seconds time elapsed

wxf@pc:/data$ perf stat -e cs -e L1-dcache-load-misses java ConcurrentAdder
value:100000000
elapsed(ms):5749Performance counter stats for 'java ConcurrentAdder 2 100 1000000':55,522 cs                                                          28,160,673 L1-dcache-load-misses                                       5.811499179 seconds time elapsed

我们能够看出,同步累加的上下文切换是要比原子累加多。这个能够理解,加锁本身就会添加线程的切换。

再看,原子累加器的L1缓存失效比同步累加器高一个数量级

笔者茅塞顿开,原子操作会导致缓存一致性问题。从而导致频繁的缓存行失效。缓存一致性协议MESI见:http://en.wikipedia.org/wiki/MESI_protocol

可是这时同步累加器在一个CPU周期内重复的获取锁操作。缓存并没有失效。

再把每次累加的线程ID输出来,会发现。原子累加的线程分布要分散非常多。

回到问题上来。为什么我们会一直觉得原子操作比加锁要快呢?文中的样例是非常特别非常特别的,正常业务场景下,我们累加过后,要经过非常多业务代码逻辑才会再次去累加,这里已经跨过非常多个CPU时间片了。从而同步累加器非常难一直获取到锁。这中情况下,同步累加器即会有等待加锁的性能损失还会有缓存一致性带来的性能损失。

所以在一般的情况下,同步累加器会慢非常多。

版权声明:本文博客原创文章。博客,未经同意,不得转载。

转载于:https://www.cnblogs.com/hrhguanli/p/4740290.html

谈论Java原子变量和同步的效率 -- 颠覆你的生活相关推荐

  1. Java 原子变量类

          今天看了下 Java中的同步机制,刚刚开始看,内容比较浅,其中有一个被称为原子变量类的东西感觉还是很有意思的,所以,记录一下吧.       首先,我们先看一段这样的代码: public ...

  2. java原子变量的作用_AtomicInteger原子类的作用介绍(代码示例)

    本篇文章给大家带来的内容是关于AtomicInteger原子类的作用介绍(代码示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. AtomicInteger 原子类的作用 多线程操 ...

  3. java原子变量的作用_原子变量 - jock_javaEE - 博客园

    一.原子变量的作用:解决并发下多个线程操作共享数据存在,线程安全问题 原子变量 = volatile + CAS算法[又叫无锁机制] 二. 例子 package com.atguigu.juc; im ...

  4. Java:多线程(同步死锁、锁原子变量、线程通信、线程池)

    5,同步和死锁 5.1,线程同步 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象: 修饰一个方法,被修饰的方法称为同步方法,其作用 ...

  5. JAVA 并发编程实践 - 原子变量与非阻塞同步机制 笔记

    2019独角兽企业重金招聘Python工程师标准>>> 非阻塞算法: 利用底层的源自机器指令(比如CAS)代替锁来实现数据在并发访问中的一致性.应用于:操作系统和JVM中实现线程/进 ...

  6. 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类

    这篇说说java.util.concurrent.atomic包里的类,总共12个,网上有很多文章解析这几个类,这里挑些重点说说. 这12个类可以分为三组: 1. 普通类型的原子变量 2. 数组类型的 ...

  7. Java多线程(二)之Atomic:原子变量与原子类

    一.何谓Atomic? Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位.计算机中的Atomic是指不能分割成若干部分的意思.如果一段代码被认为是Atomic,则表示这段代码在执行过程中 ...

  8. Java™ 教程(原子变量)

    原子变量 java.util.concurrent.atomic包定义了支持单个变量的原子操作的类,所有类都有get和set方法,类似于对volatile变量的读写操作,也就是说,set与在同一个变量 ...

  9. java cas原理_Java并发之原子变量及CAS算法-上篇

    Java并发之原子变量及CAS算法-上篇 编辑 ​ 概述 本文主要讲在Java并发编程的时候,如果保证变量的原子性,在JDK提供的类中是怎么保证变量原子性的呢?.对应Java中的包是:java.uti ...

最新文章

  1. 无所不答的“自动聊天AI”
  2. springboot整合springSecurity使用
  3. 铝聚合物电池工作温度及低温电量差异的说明
  4. Akka入门(一)Akka简介与为什么使用Akka
  5. 02.Android之IPC机制问题
  6. tcp port numbers reused出现原因_谈谈 TCP 的 TIME_WAIT
  7. python3文件的编码类型是什么_python3.x - 怎么查看某个文件的编码方式?python
  8. python爬虫网页pdf_爬虫实战【3】Python-如何将html转化为pdf(PdfKit)
  9. ip地址合法性检查 php,php检查验证IP为有效IP地址函数
  10. 你还在为查询滑动窗口最大值发愁吗?点开看最高效率解法!
  11. 【免费毕设】基于Web的subversion用户管理系统(系统+论文)
  12. 中断 http请求 正在加载 取消http请求
  13. 图片垂直居中 和 float
  14. Redis--blpop命令使用
  15. ip软件在生活中器到哪些作用呢?
  16. 智能中医诊疗系统php代码,中医HIS
  17. usb大容量储存设备感叹号?USB大容量存储设备无法启动
  18. C语言习题二:比赛评分系统
  19. 跟着Cell学作图| 11.Ingenuity Pathway Analysis(IPA)
  20. BZOJ2434【NOI2011】阿狸的打字机 AC自动机+Fail树+树状数组

热门文章

  1. python求最小值不能使用min和sotred_python基础——内置函数
  2. anaconda下载太慢怎么办_BBTIME我爱上了 Anaconda
  3. .net 宏定义_C语言、嵌入式中一些实用的宏技巧
  4. 图神经网络(一)图信号处理与图卷积神经网络(1)矩阵乘法的三种方式
  5. uilabel 自行撑开高度_UILabel文本高度计算的那些事儿
  6. Leetcode 2 两数相加 Python
  7. 曼哈顿距离最小生成树
  8. 【cudnn】cudnn 安装
  9. (Z) Linux与BSD之间的10个不同之处
  10. 如果没有网上购物,商铺价格会不会更高?