java 'i++' 计数器的性能测试

java ‘i++’ 计数器的性能测试

  • 所谓的volatile
  • Synchronized同步原语
  • JDK1.5的AtomicLong
  • Java8 LongAdder Vs AtomicLong
  • 总结:比较,该用哪个 ?

前言

在写多线程中,我们免不了使用到计数器,今天就来分析下java中提供给我们的计数器以及它们的性能测试。


1.所谓的volatile

上一篇文章我也写到了volatile的作用:当我们写一个变量时,它会被立刻刷新到主内存中去,保证了变量对其他线程的可见性,不会发生线程在自己的私有内存中更新了数据却没有同步到主内存中。并且在后来的JDK版本volatile语义被增强,它会限定部分内存重排的规则来保证线程安全性

虽然volatile用来保证线程安全性,但是我们要注意volatile修饰的变量并不是线程安全的。也就是说volatile只是起 辅助 作用,它不保证修饰的变量是原子的。

来看段简单的代码:

 public class JavaVolatile {
    volatile static int i = 0;
    public static void main(String args[]) throws InterruptedException{
        class NewThread extends Thread {
            public void run() {
                for (int j = 0; j < 1000000; ++j) {
                    ++i;
                }
            }
        }
        double start = System.currentTimeMillis();
        NewThread nt1 = new NewThread();
        NewThread nt2 = new NewThread();
        NewThread nt3 = new NewThread();
        nt1.start();
        nt2.start();
        nt3.start();
        nt1.join();
        nt2.join();
        nt3.join();
        double end = System.currentTimeMillis();
        System.out.println(i);
        System.out.println("time:" + (end-start));
    }
}

从图片我们可以看出volatile修饰的变量是不保证原子性的(正确结果应该是30000),原因比如线程A和线程B同时操作i时,A,B线程同时更新了本地,然后两个线程同时刷新到内存中问题就出现了。但是随着JVM的优化,volatile的用处也越来越少了,我们可以使用后面将要说的几种,然而一般用volatile修饰boolean类型是个不错的主意。

注意:volatile修饰和自己本身无关的变量操作时是原子的,n++不是,但若n = m+1或者 n = ture是原子的,原因可以想想volatile的原理


2.Synchronized同步原语

Synchronized可以修饰一个方法或者一个代码块,并且在某一时刻保证只有一个线程可以访问这个方法或者代码块。

Synchronized原理其实就是java中每个对象都有一个监视器,或叫做锁,当访问该对象Synchronized方法或代码块是就会上锁,直到访问完毕或者抛出异常才会释放锁。

由此可见Synchronized是保证能同步的,毕竟涉及到了lock锁机制,但是效率相对来说也是比较低的,毕竟涉及到加锁和解锁,而且在加锁的情况下其他线程访问也会被阻塞。

代码

public class JavaSynchronize {
    public static int i = 0;
    static synchronized void incre(){
        ++i;
    }
    public static void main(String args[]) throws InterruptedException {
        class NewThread extends Thread{
            public void run(){
                for(int j = 0; j < 1000000; ++j){
                    incre();
                }
            }
        }
        double start = System.currentTimeMillis();
        NewThread nt1 = new NewThread();
        NewThread nt2 = new NewThread();
        NewThread nt3 = new NewThread();
        nt1.start();
        nt2.start();
        nt3.start();
        nt1.join();
        nt2.join();
        nt3.join();
        double end = System.currentTimeMillis();
        System.out.println(i);
        System.out.println(end-start);
    }
}

结果:


3.JDK1.5的AtomicLong

java se5引入了AtomicLong这个原子类,为我们封装了类似i++的操作,所以我们可以直接简单的使用。

public class JavaThread {
    public static void main(String args[]) throws InterruptedException {
        //AtomicInteger i = new AtomicInteger(0);
        AtomicLong i = new AtomicLong(0);
        class NewThread extends Thread{
            public void run(){
                for(int j = 0; j < 1000000; ++j) {
                    i.addAndGet(1);
                }
            }
        }
        double start = System.currentTimeMillis();
        NewThread nt1 = new NewThread();
        NewThread nt2 = new NewThread();
        NewThread nt3 = new NewThread();
        nt1.start();
        nt2.start();
        nt3.start();
        nt1.join();
        nt2.join();
        nt3.join();
        double end = System.currentTimeMillis();
        System.out.println(i.get());
        System.out.println(end-start);
    }
}

代码中我注释了AtomicInteger,是原子整型,AtomicLong是长整型,在实际测试过程中AtomicInteger速度要比AtomicLong快

下图是AtomicInteger

所以,我们是选择计数器时还是要根据实际情况选择并且根据自己的机器情况来选择效率最高的。


4.Java8 LongAddr Vs AtomicLong

最新的java8更新了不少东西,其中就包括新的原子计数器LongAddr,既然它能更新进来说明它的效率是更好的^_^,使用和前面的AtomicLong没什么区别,而且它其实就是用来代替AtomicLong的。

看代码:

public class JavaLongAddr {
    public static void main(String args[]) throws InterruptedException {
        LongAdder la = new LongAdder();
        class CountThread extends Thread{
            public void run(){
                for(int i = 0; i < 1000000; ++i){
                    la.increment();
                }
            }
        }
        double start = System.currentTimeMillis();
        CountThread ct1 = new CountThread();
        CountThread ct2 = new CountThread();
        CountThread ct3 = new CountThread();
        ct1.start();
        ct2.start();
        ct3.start();
        ct1.join();
        ct2.join();
        ct3.join();
        double end = System.currentTimeMillis();
        System.out.println(la);
        System.out.println(end-start);
    }
}

我测试了多次取了个平均的是50ms左右,实际上最快达到了30ms,说明了LongAdder的效率和AtomicLong的效率相比是非常高的,快了不只一倍。但java8并没有所谓的IntegerAdder,只增加了LongAdder和DoubleAdder,如果感兴趣为什么变高效了可在网上搜索,有很多^_^。

如果想进一步了解AtomicLong和LongAddr请见http://www.importnew.com/9560.html

分析的很清楚


5.总结:比较,该用哪个?

以我的观点来说,还是具体情况具体分析,每个人需求不同,所处环境也不一样,如果我们想要自己的程序达到最大的效率话,那就动手测试吧!亲自找到效率最高的吧!,我也只能提供个思路和大致的情况。

下面是我的测试,每种取 5组数据(单位ms):

ThreadNum Synchronzied AtomicLong LongAddr
1 36/42/39/33/32 29/52/26/30/28 23/28/29/37/24
2 60/68/104/69/51 66/97/80/96/100 57/42/46/37/46
4 153/281/276/178/235 144/160/145/149/121 63/62/59/65/71
8 497/587/168/245/531 447/385/240/351/310 116/103/93/99/91
16 366/549/767/724/568 623/890/835/554/886 174/157/153/176/163

对上面的数据进行平均值计算,方便大家能更好的看出来

ThreadNum Synchronized AtomicLong LongAdder
1 36 33 28
2 70 88 46
4 225 144 64
8 406 347 100
16 595 758 165

结果很明显了,LongAdder的效率明显高过AtomicLong和Synchronized,但是在单线程的情况下差别并不大,偶尔另外两个还要高于LongAdder,多线程下 计数器肯定用LongAdder了,效率差了不是一点点,但是AtomicInteger还有待于测试。

另外如果我们想多线程同步的操作不仅仅是计数器,还有其他操作等,可以考虑选择Synchronized。

java 'i++' 计数器的性能测试

时间: 2024-07-30 19:56:24

java 'i++' 计数器的性能测试的相关文章

介绍4款json的java类库 及 其性能测试

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式. 易于人阅读和编写.同时也易于机器解析和生成. 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集. JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言. 下面介绍四款处理json的java类库:Json-lib.Gson.Jackson.Fastjson

Java队列集合的性能测试

同时开10个线程存入和取出100万的数据,结论如下: DoubleBufferedQueue < ConcurrentLinkedQueue < ArrayBlockingQueue < LinkedBlockingQueue 执行结果如下: 100万 DoubleBufferedQueue入队时间:9510 出队时间:10771 100万 DoubleBufferedQueue入队时间:8169 出队时间:97891000万 DoubleBufferedQueue入队时间:98285

Java Go Python简单性能测试

1. java(28秒) import java.util.Date; public class fff { public static void main(String[] args) { Date start = new Date(); long a = fibo(50); System.out.println(a); //25172538050 Date end = new Date(); int j = (int) (end.getTime() - start.getTime()); S

LoadRunner性能测试结果计数器指标说明

LoadRunner性能测试结果计数器指标说明 转载2015-09-23 09:57:13 标签:loadrunner计数器 针对性能测试结果分析过程中,面对大量的测试数据,反而感觉无从下手分析.今天我们就Windows操作系统计数器中的个别被监控对象进行简单的说明. Memory: ·Available Mbytes 简述:可用物理内存数.如果Available Mbytes的值很小(4 MB或更小),则说明计算机上总的内存可能不足,或某程序没有释放内存. 参考值:4 MB或更小,至少要有10

Java调优

Java调优经验谈 对于调优这个事情来说,一般就是三个过程: 性能监控:问题没有发生,你并不知道你需要调优什么?此时需要一些系统.应用的监控工具来发现问题. 性能分析:问题已经发生,但是你并不知道问题到底出在哪里.此时就需要使用工具.经验对系统.应用进行瓶颈分析,以求定位到问题原因. 性能调优:经过上一步的分析定位到了问题所在,需要对问题进行解决,使用代码.配置等手段进行优化. Java调优也不外乎这三步. 此外,本文所讲的性能分析.调优等是抛开以下因素的: 系统底层环境:硬件.操作系统等 数据

性能测试–性能监视器

性能测试–性能监视器 性能计数器(counter)是描述服务器或操作系统性能的一些数据指标.计数器在性能测试中发挥着“监控和分析”的关键作用,尤其是在分析系统的可扩展 性.进行性能瓶颈的定位时,对计数器的取值的分析非常关键.但必须说明的是,单一的性能计数器只能体现系统性能的某一个方面,对性能测试结果的分析必须基于多个不同的计数器. 与性能计数器相关的另一个术语是“资源利用率”.该术语指的是系统各种资源的使用状况.为了方便比较,一般用“资源的实际使用/总的资源可用量”形成资源利用率的数据,用以进行

Java内存区域分析

程序计数器 指令运行的指示器. 每一个线程都有独立的程序计数器,互无影响,我们称这类区域为线程私有的内存. 运行Java方法,计数器记录的是正在运行的虚拟机字节码指令地址;假设运行的是native方法,这个计数器为空. 此内存区域唯一一个没有规定不论什么OutOfMemoryError的区域. 虚拟机栈 Java虚拟机栈也是线程私有的,它的生命周期与线程同样. 虚拟机栈描写叙述的是Java方法运行的内存模型:每一个方法在运行的时候都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等

几个性能测试工具/框架的比较

在这里对几个性能测试工具做出比较,包括:Jemeter,Pylot和Mul-Mechanize.不是深度用户,一天之内使用了这三个工具/框架,在这里写下一点看法. 一.略微简介: Jemeter:Java平台下老牌性能测试工具,几乎是围绕HTTP协议为核心的一款工具,功能齐全: Pylot:一款Python平台下开源的,用以测试 Web服务器性能和扩展性的工具 Mul-Mechanize:一款Python平台下开源的,用以测试 Web服务器性能和扩展性的工具 二.特点 Jemeter:1.流程简

性能测试理论基础

一.性能测试基础 1.什么是性能测试? (1)测试软件的性能表现,考量软件运行的如何.(2)一般关注时间/效率.资源占用等情况.         既要马儿快点跑,又要马儿少吃草 2.什么时候进行性能测试? (1)已通过系统测试,功能比较稳定. 3.谁关注性能? (1)用户 用户体会到的性能是软件对用户操作的响应时间,是用户从提交或输入一个url地址到系统将全部数据呈现出来的时间.(2)系统管理员和性能测试工程师    除与用户的视角一样外,还关注与系统状态相关的信息,如系统资源的使用情况,包括C