volatile变量需要减少读取次数吗
问题说明
本人在前期读Netty源码时看到这样一段源码和注释:
private boolean invokeHandler() {
// Store in local variable to reduce volatile reads.
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
此源码见于io.netty.channel.AbstractChannelHandlerContext,其中的handlerState 是一个volatile修饰的状态量。在此方法中,需要两次使用handlerState变量。在我看来,这段代码中没有必要先使用局部变量handlerState 来保存volatile修饰的成员变量handlerState 。这样写似乎更简洁:
private boolean invokeHandler() {
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
Netty这段的注释给出了增加先使用局部变量handlerState 来保存volatile修饰的成员变量handlerState这行代码的理由:Store in local variable to reduce volatile reads。其意思为,存入本地变量来减少volatile类型属性的读取。
大概解释
鄙人多方查找资料,寻找这样做的原因,下面的这段解释似乎合理:
在编程中,减少对易变(volatile)变量的读取次数可以是一种优化技术,尤其是在并发编程中。这是因为频繁地从易变变量中读取数据可能会引入不必要的延迟,尤其是在高竞争的环境下。为了提高性能和减少延迟,你可以通过将易变变量的值存储在局部变量中来减少对它的直接访问。
为什么要减少易变变量(volatile)的读取?
性能优化:频繁读取易变变量可能会引入不必要的缓存失效和总线流量,特别是在多核处理器上。
避免竞态条件:虽然易变性本身是为了解决可见性问题,但过多的读取可能导致竞态条件的风险增加。
代码验证
为了严重在并发环境下频繁访问volatile变量时,直接读取和先读取存入局部变量两种方式性能差异,本人采用如下代码验证。
测试结果显示两种方式耗时差不多!对于这样的结果,是我的验证方式不准确,还是Netty源码中的先将volatile变量存入本地局部变量的做法是冗余代码,欢迎大家批评指正。
public class VolatileTest {
static volatile int num = 5;
static CountDownLatch latch = new CountDownLatch(20);
public static void main(String[] args) throws InterruptedException {
// 创建一个包含10个线程的固定线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//向线程池提交20个任务
long start = System.currentTimeMillis();
for (int i = 0; i < 20; i++) {
executor.execute(() -> {
for(int x = 0 ;x < 1000000 ; x++){
//方式一:直接每次读取num来使用
/*
* if(VolatileTest.num == 10){
*
* }
*
* if(VolatileTest.num > 5){
*
* }
*
* if(VolatileTest.num >7){
*
* }
* */
//方式二:先读取num存入本地的局部变量
int numLocal = VolatileTest.num;
if(numLocal == 10){
}
if(numLocal > 5){
}
if(numLocal >7){
}
}
latch.countDown();
});
}
latch.await();
long stop = System.currentTimeMillis();
System.out.println(stop-start);
// 关闭线程池
executor.shutdown();
}
}