我们认为,由于思维定式原子变量总是比同步运行的速度更快,我想是这样也已经,直到实现了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原子变量和同步的效率 -- 颠覆你的生活的更多相关文章

  1. Java原子变量

    实现全局自增id最简单有效的方式是什么?java.util.concurrent.atomic包定义了一些常见类型的原子变量.这些原子变量为我们提供了一种操作单一变量无锁(lock-free)的线程安 ...

  2. Java原子变量类需要注意的问题

    在学习多线程时,遇到了原子变量类,它是基于 CAS 和 volatile 实现的,能够保障对共享变量进行 read-modify-write 更新操作的原子性和可见性.于是我就写了一段代码试试,自认为 ...

  3. 全面了解 Java 原子变量类

    目录   一.原子变量类简介  二.基本类型  三.引用类型  四.数组类型  五.属性更新器类型  参考资料

  4. 基础篇:JAVA原子组件和同步组件

    前言 在使用多线程并发编程的时,经常会遇到对共享变量修改操作.此时我们可以选择ConcurrentHashMap,ConcurrentLinkedQueue来进行安全地存储数据.但如果单单是涉及状态的 ...

  5. 用Java原子变量的CAS方法实现一个自旋锁

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5999610. ...

  6. Java并发编程实战 第15章 原子变量和非阻塞同步机制

    非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatil ...

  7. 《Java并发编程实战》第十五章 原子变量与非堵塞同步机制 读书笔记

    一.锁的劣势 锁定后假设未释放.再次请求锁时会造成堵塞.多线程调度通常遇到堵塞会进行上下文切换,造成很多其它的开销. 在挂起与恢复线程等过程中存在着非常大的开销,而且通常存在着较长时间的中断. 锁可能 ...

  8. java并发编程(8)原子变量和非阻塞的同步机制

    原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用 ...

  9. Java多线程并发编程之原子变量与非阻塞同步机制

    1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞 ...

随机推荐

  1. Linux学习:netstat命令

    Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态等.对于开发来说,很多时候用于查看端口占用情况. 执行netstat命令,其输出结果可以分成两部分: 1)一是“Active ...

  2. 生成唯一32位ID编码代码Java(GUID)

    源码下载链接:http://pan.baidu.com/s/1jGCEWlC 扫扫关注"茶爸爸"微信公众号 坚持最初的执着,从不曾有半点懈怠,为优秀而努力,为证明自己而活. /* ...

  3. 包子IT面试培训

    包子IT面试培训 IT 面试不再怕,包子帮你圆个梦!

  4. iOS中谓词的使用

    Cocoa提供了一个类NSPredicate类,该类主要用于指定过滤器的条件,该对象可以准确的描述所需条件,对每个对象通过谓词进行筛选,判断是否与条件相匹配.谓词表示计算真值或假值的函数.在cocoa ...

  5. iReport5.6.0 linechart 制作方法

    iReport 官网和文档上关于chart设计以饼图和JDBC源作为样例.但很多其它的情况下因为报表中的数据须要首先加工处理,因此很多其它的是从JavaBeans set datasource从获取数 ...

  6. 工作需要稍微研究了一下Hyper-V

    Hyper-V是指微软的虚拟化技术,底层用得Hypervisior好像没有特殊的名字 虚拟化市场占有率前三:VMWare,Hyper-V,Citrix Citrix的XenServer今年彻底开源,原 ...

  7. A - Prime Ring Problem(素数环,深搜,打表)

    Description A ring is compose of n circles as shown in diagram. Put natural number 1, 2, ..., n into ...

  8. C++,对象成员的访问

    成员变量和成员函数的访问可以采用以下几种访问方式:对象.成员变量名: 对象.成员函数名(实参列表)对象的指针->成员变量名; 对象的指针->成员函数名(实参列表)对象的引用.成员变量名对象 ...

  9. hibernate报错

    报错二:java.lang.ExceptionInInitializerError java.lang.ExceptionInInitializerError at com.java1234.serv ...

  10. Easyui中tree组件实现搜索定位功能及展开节点定位

    这几天遇到个input + tree  实现搜索功能的需求,在这里贴出来供大家参考下,如果你有更好的实现效果希望不腻赐教! 首先给大家看看效果     小二 上图  : 需要的部件知识: easyui ...