谈论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.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞 ...
随机推荐
- spring jdbc 笔记3
spring JDBC 加入对commons-dbcp spring-jdbc spring-tx的依赖 1.数据源的配置 获取数据源在spring中的Bean管理默认已经是单例模式 关闭数据源d ...
- maven GroupID和ArtifactID填什么
GroupID是项目组织唯一的标识符,实际对应JAVA的包的结构,是main目录里java的目录结构. ArtifactID就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称.一般Gro ...
- poj 1056 IMMEDIATE DECODABILITY(KMP)
题目链接:http://poj.org/problem?id=1056 思路分析:检测某字符串是否为另一字符串的前缀,数据很弱,可以使用暴力解法.这里为了练习KMP算法使用了KMP算法. 代码如下: ...
- Air Raid(最小路径覆盖)
Air Raid Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 7511 Accepted: 4471 Descript ...
- 一、cocos2dx之如何优化内存使用(高级篇)
本文由qinning199原创,转载请注明:http://www.cocos2dx.net/?p=93 一.内存优化原则 为了优化应用内存,你应该知道是什么消耗了你应用的大部分内存,答案就是Textu ...
- Service初步了解
1.Service什么 Service它是一个应用程序组件,Android其中的四个核心组件之间 Service没有图形界面 通过经常使用来处理一些比较长耗时的操作 可以使用Service更新Cont ...
- ViewPager引导页效果实例源码
首先大家先找到本地的sdk,然后找到Google提供的API,具体查找方法如下:sdk——>docs——>index.html——>develop——>training——&g ...
- UILabel可以显示html文本
NSString * htmlString = @"<html><body> Some html string \n <font size=\"13\ ...
- Android Studio入门到精通
链接地址:http://blog.csdn.net/yanbober/article/details/45306483 目标:Android Studio新手–>下载安装配置–>零基础入门 ...
- Activity中Menu相关的几个方法的调用时机
用于创建菜单的常用的方法有如下两种: 1.onCreateOptionsMenu(Menu menu) 2.onPrepareOptionsMenu(Menu menu) MyDiaryActivit ...