谈论Java原子变量和同步的效率 -- 颠覆你的生活
我们认为,由于思维定式原子变量总是比同步运行的速度更快,我想是这样也已经,直到实现了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() {
@Override
public 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() {
@Override
public 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):8580 Performance 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):5749 Performance 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时间片了。从而同步累加器非常难一直获取到锁。这中情况下,同步累加器即会有等待加锁的性能损失还会有缓存一致性带来的性能损失。
所以在一般的情况下,同步累加器会慢非常多。
版权声明:本文博客原创文章。博客,未经同意,不得转载。
谈论Java原子变量和同步的效率 -- 颠覆你的生活的更多相关文章
- Java原子变量
实现全局自增id最简单有效的方式是什么?java.util.concurrent.atomic包定义了一些常见类型的原子变量.这些原子变量为我们提供了一种操作单一变量无锁(lock-free)的线程安 ...
- Java原子变量类需要注意的问题
在学习多线程时,遇到了原子变量类,它是基于 CAS 和 volatile 实现的,能够保障对共享变量进行 read-modify-write 更新操作的原子性和可见性.于是我就写了一段代码试试,自认为 ...
- 全面了解 Java 原子变量类
目录 一.原子变量类简介 二.基本类型 三.引用类型 四.数组类型 五.属性更新器类型 参考资料
- 基础篇:JAVA原子组件和同步组件
前言 在使用多线程并发编程的时,经常会遇到对共享变量修改操作.此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据.但如果单单是涉及状态的 ...
- 用Java原子变量的CAS方法实现一个自旋锁
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5999610. ...
- Java并发编程实战 第15章 原子变量和非阻塞同步机制
非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatil ...
- 《Java并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记
一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...
- java并发编程(8)原子变量和非阻塞的同步机制
原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用 ...
- Java多线程并发编程之原子变量与非阻塞同步机制
1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞 ...
随机推荐
- C++惯用法:通过成员模板实现隐式转换(Coercion 强迫 by Member Template)
Intent To increase the flexibility of a class template's interface by allowing the class template to ...
- 有n个数(两两不同),对于这n个数的每个连续子序列,把其中最大的一个数标记一次,问最后每个数被标记次数
今天在qq群了看到了这个题目,觉得用单调栈的解法挺好,可以在o(n)内搞定,特意记录下来 首先明确单调栈的含义: 栈是FILO的,栈的所有操作都是在栈顶进行. 单调性指的是当前栈中存储的元素是严格的递 ...
- 一个包含所有c++的头文件的头文件
#include <bits/stdc++.h> 做CF看见别人用这个函数,然后就能直接用vector,set,string那些函数了,摸不着头脑,感觉特神奇就百度了一下,才发现这个是C+ ...
- 移动端rem,scale动态设置
pt:物理像素(电容屏上像素块个数) px:逻辑像素.设备独立像素 高清屏:1px = 4pt 普通屏:1px = 1pt dpr:设备像素比:(某一方向上)物理像素/逻辑像素 通常设置1rem=屏幕 ...
- 【IE】trim()方法失效
今天用了$.ajax异步提交,结果在ie8里面报错了,说不支持此对象,找了半天没找到什么问题. 后来发现是我data里面的参数传递,里面有个参数用到了trim()方法,这个方法在ie8里面是失效的. ...
- 树莓派安装mysql
首先我想启用root用户,所以我先启用root用户: sudo passwd root 这里会提示输入两次密码,然后: sudo passwd --unlock root 这样就可以启动root登录, ...
- JQuery插件使用小结
JQuery插件使用小结
- HDU 5009 DP
2014 ACM/ICPC Asia Regional Xi'an Online 对于N个数 n(1 ≤ n ≤ 5×104), 把N个数分成随意个区间,每一个区间的值是该区间内不同数字个数的平方和, ...
- ACM 做题过程中的一些小技巧。
ACM做题过程中的一些小技巧. 1.一般用C语言节约空间,要用C++库函数或STL时才用C++; cout.cin和printf.scanf最好不要混用. 2.有时候int型不够用,可以用long l ...
- 安装MyEclipse Color Themes
下载地址:http://eclipsecolorthemes.org/?list=toppicks&lang=html 安装步骤如下: 1.Import---Preferences 2.选择下 ...