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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • OpenCV-Python (Sobel算子)[通俗易懂]

    OpenCV-Python (Sobel算子)[通俗易懂]Sobel算子Sobel算子的基本概念Sobel算子是一个主要用于边缘检测的离散微分算子(discretedifferentiationoperator)。它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任意一点使用此算子,都将会产生对应的梯度矢量或是其法矢量。原型Sobel算子依然是一种过滤器,只是其是带有方向的。在OpenCV-Pyt…

    2022年7月14日
    21
  • reaver 和 aircrack-ng 安装与使用

    reaver 和 aircrack-ng 安装与使用安装1.下载reaver最新版本(这里用1.4) 2.解压: tar-xvfreaver-1.4.tar.gz3.cdreaver-1.4/src4../configure5.sudomake6.sudomakeinstall如果缺少库的话在./configure 时会有提示。我在安装时提示缺少pcaplibrary.于是s

    2022年5月27日
    45
  • 傅里叶变换 意义_傅里叶变换表达式

    傅里叶变换 意义_傅里叶变换表达式看到论坛有一个朋友提问为什么傅里叶变换可以将时域变为频域?这个问题真是问到了灵魂深处。在这我只能简单讲讲我的理解,要深刻理解翻信号处理教科书是最好的方法。1.如何描述信号我们常常用数学模型去抽象物理事件。信号也可以用数学模型来表示。有了信号的数学模型,我们就可以利用数学计算对信号模型做各种各样的改变。如果加以计算机,模电,数电的相关知识,我们就可以将我们对信号模型的改变转换为对物理信号的…

    2022年10月20日
    1
  • LLDP技术原理

    LLDP技术原理概念:LLDP(LinkLayerDiscoveryProtocol,链路层发现协议)提供了一种标准的链路层发现方式,可以将本端设备的的主要能力、管理地址、设备标识、接口标识等信息组织成不同的TLV(Type/Length/Value),并封装在LLDPDU(LinkLayerDiscoveryProtocolDataUnit,链路层发现协议数据单元)中发布给与自己直连的邻居,邻居收到这些信息后将其以标准MIB(ManagementInformationBase,管理信息库)的形式保存

    2022年5月8日
    46
  • 集合类型python_python基础知识

    集合类型python_python基础知识集合集合的特点:是一种可迭代的、无序的、不能包含重复元素的数据结构去重b=[10,5,6,1,9,1]c=set(b)print(c)>>>{1,5

    2022年7月30日
    5
  • Kotlin概述与Java的比较

    Kotlin概述与Java的比较Kotlin是JetBrains的一种新的编程语言。它首次出现在2011年,JetBrains推出了名为“科特林”的项目。Kotlin是开源语言。基本上像Java一样,C和C++-Kotlin也是“静态类型编程语言”。静态类型的编程语言是在使用变量之前不需要定义的那些语言。这意味着静态类型与变量的使用明确声明或初始化有关。如前所述,Java是静态类型语言的一个例子,类似C和C++

    2022年7月8日
    20

发表回复

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

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