Java volatile的性能分析
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
volatile通過內(nèi)存屏障來實現(xiàn)禁止重排序,通過Lock執(zhí)行來實現(xiàn)線程可見性,如果我們的程序中需要讓其他線程及時的對我們的更改可見可以使用volatile關(guān)鍵字來修飾,比如AQS中的state

所以在一個線程寫,多個線程讀的情況下,或者是對volatile修飾的變量進行原子操作時,是可以實現(xiàn)共享變量的同步的,但是i++ 不行,因為i++ 又三個操作組成,先讀出值,然后再對值進行+1 ,接著講結(jié)果寫入,這個過程,如果中間有其他線程對該變量進行了修改,那么這個值就無法得到正確的結(jié)果。
今天我們討論的重點不是他的功能,而是他的性能問題,首先我們可以看下我們對非volatile變量進行操作,循環(huán)+1,多個線程操作多個變量(這里不存在并發(fā),至于為什么要多個線程跑,后面就知道了)
首先定義一個Data,內(nèi)容是四個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);
}
}這樣的結(jié)果是:608ms
接著我們用volatile修飾long:
class Data {
public volatile long value1 ;
public volatile long value2;
public volatile long value3;
public volatile long value4;
}運行結(jié)果為:66274
可以看出是100倍左右,使用volatile的性能為什么會這么差呢,原因是因為,因為volatile的讀和寫都是要經(jīng)過主存的,讀會廢棄高速緩存的地址,從緩存讀,寫也會及時刷新到主存
那么我們用一個線程操作一個變量試試呢:結(jié)果是:5362
是要好很多,為什么多線程情況下差距這么大呢,我們并沒有進行并發(fā)操作,并沒有鎖,那是因為發(fā)生了偽共享,CPU的高速緩存的最小單位是緩存行,一般是64 byte,這個CPU核心私有的,當我們的cpu核心1 跑線程0 , 核心2跑線程1的時候,因為局部性原理,core1的L1緩存將value1加載到緩存,也會將后面的幾個一并加載進來,core2也一樣,也就是說,core1和core2的緩存差不多都把四個值保存了,而緩存行中如果一個值發(fā)生變化,cpu會吧整個緩存行重新加載,那么可以理解下,因為內(nèi)存的一致性,就會導致各個核心不停的從主存加載和刷新,這就導致了性能的問題。
怎么解決呢:
1.將值拷貝至線程內(nèi)部操作,完成后進行賦值操作,也就是Data中的值依然使用volatile修飾,線程的執(zhí)行邏輯改為:
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 ;
} );這個結(jié)果是多少呢:76ms,可以看到這個比不用volatile修飾還要快很多,那是因為線程私有的可以直接在線程內(nèi)部棧內(nèi)存操作,時間就是cpu消耗的時間,并不會發(fā)生內(nèi)存耗時
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字節(jié)
邏輯不變,依然是線程直接操作value,而不是拷貝到內(nèi)部:
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;
}
} );這個結(jié)果是:44ms,比在線程內(nèi)部操作還要快。
————————————————
版權(quán)聲明:本文為CSDN博主「dw147258dw」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/dw147258dw/article/details/115006626
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
