Java volatile的性能分析「建议收藏」

Java volatile的性能分析「建议收藏」volatile通过内存屏障来实现禁止重排序,通过Lock执行来实现线程可见性,如果我们的程序中需要让其他线程及时的对我们的更改可见可以使用volatile关键字来修饰,比如AQS中的state所以在一个线程写,多个线程读的情况下,或者是对volatile修饰的变量进行原子操作时,是可以实现共享变量的同步的,但是i++不行,因为i++又三个操作组成,先读出值,然后再对值进行+1,接着讲结果写入,这个过程,如果中间有其他线程对该变量进行了修改,那么这个值就无法得到正确的结果。今天我们讨论的重

大家好,又见面了,我是你们的朋友全栈君。

volatile通过内存屏障来实现禁止重排序,通过Lock执行来实现线程可见性,如果我们的程序中需要让其他线程及时的对我们的更改可见可以使用volatile关键字来修饰,比如AQS中的state

Java volatile的性能分析「建议收藏」

所以在一个线程写,多个线程读的情况下,或者是对volatile修饰的变量进行原子操作时,是可以实现共享变量的同步的,但是i++ 不行,因为i++ 又三个操作组成,先读出值,然后再对值进行+1 ,接着讲结果写入,这个过程,如果中间有其他线程对该变量进行了修改,那么这个值就无法得到正确的结果。

今天我们讨论的重点不是他的功能,而是他的性能问题,首先我们可以看下我们对非volatile变量进行操作,循环+1,多个线程操作多个变量(这里不存在并发,至于为什么要多个线程跑,后面就知道了)

首先定义一个Data,内容是四个long类型的变量,我们将会使用四个线程分别对他们进行递增计算操作:

class Data {
	public  long value1 ;
    public  long value2;
    public  long value3;
    public  long value4;
}

运行类:

public class SyncTest extends Thread{
    public static void main(String args[]) throws InterruptedException{
        Data data = new Data();
        ExecutorService es = Executors.newFixedThreadPool(4);
        long start = System.currentTimeMillis();
        int loopcont = 1000000000;
        Thread t[] = new Thread[4];
        t[0] = new Thread(()-> {
            for(int i=0;i<loopcont;i++){
                data.value1 = data.value1+i;
            }
        } );
        t[1] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value2 = data.value2+i;
            }
        } );
        t[2] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value3 = data.value3+i;
            }
        } );
        t[3] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value4 = data.value4+i;
            }
        } );
        for(Thread item:t){
            es.submit(item);
        }
        for(Thread item:t){
            item.join();
        }
        es.shutdown();
        es.awaitTermination(9999999, TimeUnit.SECONDS);
        long end = System.currentTimeMillis();
        System.out.println(end-start);  
    }
    
}

这样的结果是:608ms

接着我们用volatile修饰long:

class Data {
	public volatile long value1 ;
    public volatile long value2;
    public volatile long value3;
    public volatile long value4;
}

运行结果为:66274

可以看出是100倍左右,使用volatile的性能为什么会这么差呢,原因是因为,因为volatile的读和写都是要经过主存的,读会废弃高速缓存的地址,从缓存读,写也会及时刷新到主存

那么我们用一个线程操作一个变量试试呢:结果是:5362

是要好很多,为什么多线程情况下差距这么大呢,我们并没有进行并发操作,并没有锁,那是因为发生了伪共享,CPU的高速缓存的最小单位是缓存行,一般是64 byte,这个CPU核心私有的,当我们的cpu核心1 跑线程0 , 核心2跑线程1的时候,因为局部性原理,core1的L1缓存将value1加载到缓存,也会将后面的几个一并加载进来,core2也一样,也就是说,core1和core2的缓存差不多都把四个值保存了,而缓存行中如果一个值发生变化,cpu会吧整个缓存行重新加载,那么可以理解下,因为内存的一致性,就会导致各个核心不停的从主存加载和刷新,这就导致了性能的问题。

 

怎么解决呢:

1.将值拷贝至线程内部操作,完成后进行赋值操作,也就是Data中的值依然使用volatile修饰,线程的执行逻辑改为:

t[0] = new Thread(()-> {
        	long value = data.value1 ;
            for(int i=0;i<loopcont;i++){
            	value ++ ;
            }
            data.value1 = value ;
        } );
        t[1] = new Thread( () -> {
        	long value = data.value1 ;
            for(int i=0;i<loopcont;i++){
                value++;
            }
            data.value2 = value ;
        } );
        t[2] = new Thread( () -> {
        	long value = data.value1 ;
            for(int i=0;i<loopcont;i++){
            	value ++;
            }
            data.value3 = value ;
        } );
        t[3] = new Thread( () -> {
        	long value = data.value1 ;
            for(int i=0;i<loopcont;i++){
            	value ++;
            }
            data.value4 = value ;
        } );

这个结果是多少呢:76ms,可以看到这个比不用volatile修饰还要快很多,那是因为线程私有的可以直接在线程内部栈内存操作,时间就是cpu消耗的时间,并不会发生内存耗时

 

2使用缓存行填充

这里我们把Data里面的long修饰一下:

public class VolatileLongPadding {
    public volatile long p1, p2, p3, p4, p5, p6; // 注释  
}
 
package com.demo.rsa;
public class VolatileLong extends VolatileLongPadding {
    public volatile long value = 0L;  
}
 
class Data {
    public VolatileLong value1 = new VolatileLong();
    public VolatileLong value2= new VolatileLong();
    public VolatileLong value3= new VolatileLong();
    public VolatileLong value4= new VolatileLong();
}

这里的VolatileLong 通过volatile修饰,并填充了6个无用的long占空间,加上对象头,刚好64字节

逻辑不变,依然是线程直接操作value,而不是拷贝到内部:

Thread t[] = new Thread[4];
        t[0] = new Thread(()-> {
            for(int i=0;i<loopcont;i++){
                data.value1.value = data.value1.value+i;
            }
        } );
        t[1] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value2.value = data.value2.value+i;
            }
        } );
        t[2] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value3.value = data.value3.value+i;
            }
        } );
        t[3] = new Thread( () -> {
            for(int i=0;i<loopcont;i++){
                data.value4.value = data.value4.value+i;
            }
        } );

这个结果是:44ms,比在线程内部操作还要快。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/161263.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • pycharm多行代码同时注释、去除注释_pycharm提示快捷键

    pycharm多行代码同时注释、去除注释_pycharm提示快捷键单行和多行注释快捷键选中后:Ctrl+/(done)

  • java获取当前日期是周几_根据日期获取当前周

    java获取当前日期是周几_根据日期获取当前周1、使用Calendar类 /**    *获取当前日期是星期几    *     *@paramdt    *@return当前日期是星期几    */   publicstaticStringgetWeekOfDate(Datedt){       String[]weekDays={“星期日”,”星期一”,”星期

  • Eclipse代码自动补全方法「建议收藏」

    Eclipse代码自动补全方法「建议收藏」MyEclipse,是在eclipse基础上加上自己的插件开发而成的功能强大的企业级集成开发环境,主要用于Java、JavaEE以及移动应用的开发;以下小编给大家介绍在使用MyEclipse开发编写代码中代码提示的快捷方式;代码的提示大大给开发者带来高效的编程及代码书写的准确率等。详细步骤如下:1、找到Window下的Preferences里面的Java-Editor-Templates路径…

  • 配置zabbix时启动失败解决办法

    配置zabbix时启动失败解决办法一开始按照这篇博客来配置zabbixhttps://blog.csdn.net/rujianxuezha/article/details/79842998启动zabbix时出现以下提示[root@www~]#systemctlstartzabbix-serverJobforzabbix-server.servicefailedbecauseaconfiguredresourc…

  • oSIP开发者手册 oSIP开发者手册

    oSIP开发者手册 oSIP开发者手册摘要“会话发起协议(SessionInitiationProtocol-SIP)是一个应用层的信令控制协议。用于创建、修改和终止一个或多个参与者的会话。这些会话可以是Internet多媒体会议、IP电话或多媒体分发(例如:语音信箱)。会话的参与者可以通过组播(multicast)、网状单播(unicast)或两者的混合体进行通信。”  ”TheSessionInitiationPro

  • 如何判断一个对象是否为空{}

    如何判断一个对象是否为空{}我们想要判断对象是否为空,像基本类型那样判断是不可以的,==={}?这样是错误的,因为只是比较引用地址是否相同,所以可以采取下面的方法来进行判断1.根据for…in遍历对象,如果存在则返回true,否则返回falsefor(letiinobj){ returntrue;}returnfalse2.利用JSON自带的JSON.stringify()方法来判断…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号