用户工具

站点工具


volatile

volatile关键字的那些事儿

volatile这个关键字,大家都很熟,但是又很陌生,面试的时候,假如你问别人这个问题,我敢保证,大多数人的回答并不能令你满意,包括很多工作经验丰富的老手。其实volatile在不同的语言当中,它的意义是不一样的,首当其中的就是在c语言当中的volatile。

c语言中的volatile

在c语言当中,volatile关键字的含义就是去除编译器优化,这样可以让你的去除一些编译过程中加入的不确定性。我们来看一个例子:

#include<stdio.h>

void main(int argc,char *args){

   int i = 10;

// volatile int i = 10;

  int j = i;

  int k = i;

  printf("%d",k);

}

如果没有volatile关键字,我们来看反编译后的结果:

gcc -O1 test.c

objdump -d a.out
  000000000040050c <main>:
  40050c:	48 83 ec 18          	sub    $0x18,%rsp
  400510:	c7 44 24 0c 0a 00 00 	movl   $0xa,0xc(%rsp)
  400517:	00 
  400518:	8b 44 24 0c          	mov    0xc(%rsp),%eax
  40051c:	8b 74 24 0c          	mov    0xc(%rsp),%esi
  400520:	bf ec 05 40 00       	mov    $0x4005ec,%edi
  400525:	b8 00 00 00 00       	mov    $0x0,%eax
  40052a:	e8 b1 fe ff ff       	callq  4003e0 <printf@plt>
  40052f:	48 83 c4 18          	add    $0x18,%rsp
  400533:	c3                   	retq   
  400534:	90                   	nop
  400535:	90                   	nop
  400536:	90                   	nop
  400537:	90                   	nop
  400538:	90                   	nop
  400539:	90                   	nop
  40053a:	90                   	nop
  40053b:	90                   	nop
  40053c:	90                   	nop
  40053d:	90                   	nop
  40053e:	90                   	nop
  40053f:	90                   	nop

我们可以看到,最终mov $0x4005ec,%edi把结果直接赋给了k.这样就有可能造成潜在的不确定性。

然后我们来看加入volatile关键字的反编译结果:

  000000000040050c <main>:
  40050c:	55                   	push   %rbp
  40050d:	48 89 e5             	mov    %rsp,%rbp
  400510:	48 83 ec 20          	sub    $0x20,%rsp
  400514:	89 7d ec             	mov    %edi,-0x14(%rbp)
  400517:	48 89 75 e0          	mov    %rsi,-0x20(%rbp)
  40051b:	c7 45 f4 0a 00 00 00 	movl   $0xa,-0xc(%rbp)
  400522:	8b 45 f4             	mov    -0xc(%rbp),%eax
  400525:	89 45 fc             	mov    %eax,-0x4(%rbp)
  400528:	8b 45 f4             	mov    -0xc(%rbp),%eax
  40052b:	89 45 f8             	mov    %eax,-0x8(%rbp)
  40052e:	8b 45 f8             	mov    -0x8(%rbp),%eax
  400531:	89 c6                	mov    %eax,%esi
  400533:	bf fc 05 40 00       	mov    $0x4005fc,%edi
  400538:	b8 00 00 00 00       	mov    $0x0,%eax
  40053d:	e8 9e fe ff ff       	callq  4003e0 <printf@plt>
  400542:	c9                   	leaveq 
  400543:	c3                   	retq   
  400544:	90                   	nop
  400545:	90                   	nop
  400546:	90                   	nop
  400547:	90                   	nop
  400548:	90                   	nop
  400549:	90                   	nop
  40054a:	90                   	nop
  40054b:	90                   	nop
  40054c:	90                   	nop
  40054d:	90                   	nop
  40054e:	90                   	nop
  40054f:	90                   	nop

从上面的结果我们可以看到去除编译器优化之后,2次赋值的过程都有了。。。

java语言中的volatile

根据java内存模型中的happen-before法则,对volatile的规则定义为:

Volatile variable rule. A write to a volatile field happens-before every subsequent read of that same field.

也即是说,只要按照顺序写入Volatile变量,读取的时候也是可以保证确定性的。

但是很多人把可见性和一致性混淆在了一起,我们来看一段代码反编译后的结果:

public class Test {
private  static   volatile int  i = 0;
//private static int i = 10;
public static void main(String args[]){
      Thread t = new Thread(new Runnable(){
      public void run(){
       	for(int x=0;x<10;x++){
        System.out.println(i++);
        try{
        Thread.currentThread().sleep(100);
    }catch(Exception ex){

    }
       }
      }
      });

      t.start();

      Thread t2 = new Thread(new Runnable(){
      public void run(){
       for(int x=0;x<10;x++){
        System.out.println(i++);
        try{
        Thread.currentThread().sleep(100);
    }catch(Exception ex){
    	
    }
       }
      }
      });

      t2.start();
}

}

反编译结果为:

javap -p Test
public class Test {
  public Test();
    Code:
       0: aload_0       
       1: invokespecial #2                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class java/lang/Thread
       3: dup           
       4: new           #4                  // class Test$1
       7: dup           
       8: invokespecial #5                  // Method Test$1."<init>":()V
      11: invokespecial #6                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      14: astore_1      
      15: aload_1       
      16: invokevirtual #7                  // Method java/lang/Thread.start:()V
      19: new           #3                  // class java/lang/Thread
      22: dup           
      23: new           #8                  // class Test$2
      26: dup           
      27: invokespecial #9                  // Method Test$2."<init>":()V
      30: invokespecial #6                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      33: astore_2      
      34: aload_2       
      35: invokevirtual #7                  // Method java/lang/Thread.start:()V
      38: return        

  static int access$008();
    Code:
       0: getstatic     #1                  // Field i:I
       3: dup           
       4: iconst_1      
       5: iadd          
       6: putstatic     #1                  // Field i:I
       9: ireturn       

  static {};
    Code:
       0: iconst_0      
       1: putstatic     #1                  // Field i:I
       4: return        
}

我们可以看到static int access$008()部分的代码,读写其实是分两个过程的,volatile只保证每个线程读取变量的时候都从内存中取不走本地缓存,但是确不能保证一致性。

volatile.txt · 最后更改: 2018/10/14 15:31 (外部编辑)