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,比在线程内部操作还要快。

 

 

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

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

(0)
上一篇 2022年7月18日 上午8:46
下一篇 2022年7月18日 上午9:00


相关推荐

  • ice服务器框架压力测试数据「建议收藏」

    ice服务器框架压力测试数据「建议收藏」ice服务器框架压力测试数据标签: 服务器测试框架socket工作linux2011-07-1215:50 3819人阅读 评论(5) 收藏 举报 分类: ice(3) 版权声明:本文为博主原创文章,未经博主允许不得转载。有段时间为公司做了一些技术收集方面的工作,ice作为一个开源的网络通讯中间件,肯定是我们不错的研究对象。

    2022年6月7日
    103
  • GHOSTXPSP3电脑爱好者V9.9美化修正版

    GHOSTXPSP3电脑爱好者V9.9美化修正版全自动无人值守安装,采用万能GHOST技术,安装系统过程只需5-8分钟,适合新旧各种机型。集成DX9.0c最新版、双核补丁。集成常用VB/VC++2005、2008、2010、2012、2013/运行库、万能驱动助理稳定版安装过程会自动删除各分区下可能存在的AUTORUN病毒TcpIP连接数修改为1000;破解Uxtheme.dll可自由添加美化主题。我的文档收藏夹…

    2022年6月10日
    99
  • pytorch中tensor.expand()和tensor.expand_as()函数解读

    pytorch中tensor.expand()和tensor.expand_as()函数解读tensor expend 函数 gt gt gt importtorch gt gt gt a torch tensor 2 3 4 gt gt gt print a size torch Size 3 1 gt gt gt a expand 3 2 tensor 2 2 3 3

    2026年3月16日
    2
  • hdu 3631 Shortest Path(Floyd)[通俗易懂]

    hdu 3631 Shortest Path(Floyd)

    2022年2月1日
    41
  • 通过外网访问内网服务器

    通过外网访问内网服务器通过外网访问内网服务器 版权声明 本文为 CSDN 博主 Chao sCode 的原创文章 遵循 CC4 0BY SA 版权协议 转载请附上原文出处链接及本声明 原文链接 https blog csdn net jinzhichaosh article details 首先使用 tomcat 在电脑上建立一个服务器 怎样建立看我的另外一篇博客 此时 我的 tomcat 开放的端口是 8888 然后登录路由器的管理页面 在 cmd 中查看 ip 地址 i

    2026年3月16日
    2
  • scratch编程小游戏咬指大冒险

    scratch编程小游戏咬指大冒险咬指大冒险是一款非常好玩的玩具,玩法就是玩家不断按下小动物的牙齿,牙齿中只有一个是危险的,按下后小动物的嘴巴会闭上咬住手指,其余的牙齿都是安全的。今天我们就来用scratch来做一个电子版的咬指大冒险!这里我们选用一个鳄鱼的造型,首先画出鳄鱼的两个造型,一个张嘴和一个闭嘴,张嘴的造型不需要画下牙齿:要画大一点哦!程序:下面是牙齿的造型,一个有三个:牙齿的排列方式是一段弧,可以先排列好后再调整鳄鱼的嘴巴:最后是被咬时血液的程序:注意,这里的自定义模块需要勾选运行时屏幕不刷新,如果不

    2022年6月15日
    55

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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