`

volatile2

 
阅读更多
在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。



这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。



在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。



要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。



Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。



Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。



这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。



使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。


















Volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。如果配合Java 5增加的atomic wrapper classes,对它们的increase之类的操作就不需要sychronized。

















用在多线程,同步变量。 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。volatile就是用来避免这种情况的。volatile告诉jvm, 它所修饰的变量不保留拷贝,直接访问主内存中的(也就是上面说的A)

=========================分割线1=================================

版权声明 :转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://aleung.blogbus.com/logs/32090434.html

在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。

一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。以下例子展现了volatile的作用:
Java代码
public class StoppableTask extends Thread {  
 
  private volatile boolean pleaseStop;  
 
 
  public void run() {  
 
    while (!pleaseStop) {  
 
     // do some stuff...  
 
    }  
 
}  
 
 
  public void tellMeToStop() {  
 
   pleaseStop = true;  
 
  }  
 


public class StoppableTask extends Thread {

  private volatile boolean pleaseStop;


  public void run() {

    while (!pleaseStop) {

     // do some stuff...

    }

}


  public void tellMeToStop() {

   pleaseStop = true;

  }

}
假如pleaseStop没有被声明为volatile,线程执行run的时候检查的是自己的副本,就不能及时得知其他线程已经调用tellMeToStop()修改了pleaseStop的值。

Volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。如果配合Java 5增加的atomic wrapper classes,对它们的increase之类的操作就不需要sychronized。

Reference:
http://www.javamex.com/tutorials/synchronization_volatile.shtml
http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
=========================分割线2=================================

  恐怕比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法;看如下三句get代码:

Java代码
int i1;               
int geti1() {return i1;}   
volatile int i2;   
int geti2()  
{return i2;}   
int i3;                
synchronized int geti3() {return i3;}   
  geti1() 

int i1;            
int geti1() {return i1;}
volatile int i2;
int geti2()
{return i2;}
int i3;             
synchronized int geti3() {return i3;}
  geti1()
得到存储在当前线程中i1的数值。多个线程有多个i1变量拷贝,而且这些i1之间可以互不相同。换句话说,另一个线程可能已经改变了它线程内的i1值,而这个值可以和当前线程中的i1值不相同。事实上,Java有个思想叫“主”内存区域,这里存放了变量目前的“准确值”。每个线程可以有它自己的变量拷贝,而这个变量拷贝值可以和“主”内存区域里存放的不同。因此实际上存在一种可能:“主”内存区域里的i1值是1,线程1里的i1值是2,线程2里的i1值是3——这在线程1和线程2都改变了它们各自的i1值,而且这个改变还没来得及传递给“主”内存区域或其他线程时就会发生。
  而 geti2()得到的是“主”内存区域的i2数值。用volatile修饰后的变量不允许有不同于“主”内存区域的变量拷贝。换句话说,一个变量经 volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即获取到了相同的值。理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的变量拷贝更为高效。
  既然volatile关键字已经实现了线程间数据同步,又要 synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“ 主”内存区域同步整个线程的内存。因此,执行geti3()方法做了如下几步:
1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步。。。[后面的不知道怎么表达,汗])
3. 代码块被执行
4. 对于变量的任何改变现在可以安全地写到“主”内存区域中(不过geti3()方法不会改变变量值)
5. 线程释放监视this对象的对象锁
  因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。

=========================分割线3=================================

volatile关键字相信了解Java多线程的读者都很清楚它的作用。volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:

Java代码
package  mythread;  
 
public   class  JoinThread  extends  Thread  
{  
     public   static volatile int  n  =   0 ;  
    public   void  run()  
    {  
         for  ( int  i  =   0 ; i  <   10 ; i ++ )  
             try   
        {  
                n  =  n  +   1 ;  
                sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒   
 
            }  
             catch  (Exception e)  
            {  
            }  
    }  
 
     public   static   void  main(String[] args)  throws  Exception  
    {  
 
        Thread threads[]  =   new  Thread[ 100 ];  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  建立100个线程   
            threads[i]  =   new  JoinThread();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  运行刚才建立的100个线程   
            threads[i].start();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  100个线程都执行完后继续   
            threads[i].join();  
        System.out.println( " n= "   +  JoinThread.n);  
    }  
}  

package  mythread;

public   class  JoinThread  extends  Thread
{
     public   static volatile int  n  =   0 ;
    public   void  run()
    {
         for  ( int  i  =   0 ; i  <   10 ; i ++ )
             try
        {
                n  =  n  +   1 ;
                sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

            }
             catch  (Exception e)
            {
            }
    }

     public   static   void  main(String[] args)  throws  Exception
    {

        Thread threads[]  =   new  Thread[ 100 ];
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  建立100个线程
            threads[i]  =   new  JoinThread();
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  运行刚才建立的100个线程
            threads[i].start();
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  100个线程都执行完后继续
            threads[i].join();
        System.out.println( " n= "   +  JoinThread.n);
    }
}    
如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:

n  =  n  +   1 ;
n ++ ;

      如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:

Java代码
package  mythread;  
 
public   class  JoinThread  extends  Thread  
{  
     public   static int  n  =   0 ;  
 
     public static   synchronized   void  inc()  
    {  
        n ++ ;  
    }  
     public   void  run()  
    {  
         for  ( int  i  =   0 ; i  <   10 ; i ++ )  
             try   
            {  
                inc();  //  n = n + 1 改成了 inc();   
                sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒   
 
            }  
             catch  (Exception e)  
            {  
            }  
    }  
 
     public   static   void  main(String[] args)  throws  Exception  
    {  
 
        Thread threads[]  =   new  Thread[ 100 ];  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  建立100个线程   
            threads[i]  =   new  JoinThread();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  运行刚才建立的100个线程   
            threads[i].start();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  100个线程都执行完后继续   
            threads[i].join();  
        System.out.println( " n= "   +  JoinThread.n);  
    }  
}  

package  mythread;

public   class  JoinThread  extends  Thread
{
     public   static int  n  =   0 ;

     public static   synchronized   void  inc()
    {
        n ++ ;
    }
     public   void  run()
    {
         for  ( int  i  =   0 ; i  <   10 ; i ++ )
             try
            {
                inc();  //  n = n + 1 改成了 inc();
                sleep( 3 );  //  为了使运行结果更随机,延迟3毫秒

            }
             catch  (Exception e)
            {
            }
    }

     public   static   void  main(String[] args)  throws  Exception
    {

        Thread threads[]  =   new  Thread[ 100 ];
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  建立100个线程
            threads[i]  =   new  JoinThread();
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  运行刚才建立的100个线程
            threads[i].start();
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )
             //  100个线程都执行完后继续
            threads[i].join();
        System.out.println( " n= "   +  JoinThread.n);
    }
}
    上面的代码将n=n+1改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++ 等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
分享到:
评论

相关推荐

    单片机中volatile定义的作用

    2. 多任务环境下各任务间共享的标志应该加volatile 3. 存储器映射的硬件寄存器通常也要加volatile 此外,volatile关键字还可以用于解决一些常见的问题,例如: * 一个参数既可以是const还可以是volatile吗?答案是...

    Volatile详解,深入学习Volatile

    2. Volatile的作用: - 防止编译器优化:编译器通常会优化掉被认为不变的变量的读取,但volatile变量告诉编译器不要做这样的优化,每次使用时都从内存中读取。 - 多线程同步:在多线程环境中,当一个线程修改了...

    volatile的用法讲解

    2. volatile修饰符不能与const修饰符同时使用,因为volatile修饰符意味着变量的值可能会改变,而const修饰符意味着变量的值不能改变。 3. 使用volatile修饰符可能会影响编译器的优化,因此需要根据实际情况进行选择...

    volatile源码分析1

    2. 内存可见性 内存可见性是指当一个线程修改了共享变量后,其他线程能够立即看到这个修改。在Java中,volatile变量的写操作会将新值刷新到主内存,读操作则会从主内存读取最新值。这样就保证了多线程环境下,所有...

    const和volatile分析

    2. 多线程编程:在多线程环境下,当一个变量可能被其他线程修改时,应使用`volatile`,让编译器知道这个变量的值可能会在不通过程序控制的情况下发生变化。 3. 实时系统:在实时系统中,`volatile`用于声明受外部...

    volatile的使用方法

    2. 在使用像寄存器一样的变量时,用于指定变量的存储类别。 3. 在使用中断服务程序时,用于指定中断服务程序中使用的变量的存储类别。 volatile 关键字是 C 语言中一个非常重要的关键字,它可以帮助我们避免编译器...

    stm32 volatile变量的正确使用

    2. **多线程或多任务环境中的共享变量:** - 在RTOS环境下,多个任务或中断服务程序可能会访问同一个变量。为了确保每个任务都能获取到最新的变量值,应当使用`volatile`修饰符。例如,在一个任务中修改了一个共享...

    volatile用法

    2. `volatile`与`const`结合使用: 一个变量可以同时是`const`和`volatile`的。这意味着变量的值不能被程序修改,但可能在运行时被其他不受程序控制的因素改变。例如,某些只读的硬件寄存器就是这样的例子。 3. `...

    Java线程:volatile关键字

    Java 线程 volatile 关键字详解 Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。volatile 变量的同步性较差,但它有时更简单并且开销更低。volatile 变量可以被看作是一种 “程度较轻的 ...

    volatile详解

    2. **volatile指针**:指针也可以被声明为`volatile`,这在中断服务子程序中修改指向缓冲区的指针时非常有用。例如,`volatile char *bufferPointer;`可以用来表示一个可能被外部因素修改的指针。 3. **函数示例...

    单片机C语言中volatile的作用

    2. **一个指针可以是volatile吗?解释为什么。** - 答案同样是肯定的。虽然这种情况不太常见,但在某些特定情况下确实存在。例如,当一个中断服务子程序修改了一个指向缓冲区的指针时,这个指针就需要被声明为`...

    volatile的用法

    2. 多任务环境下的用法:在多任务系统中,如果有多个任务共享同一个变量,为了保证变量值的实时性和准确性,应当将共享变量声明为volatile。这样,无论何时读取该变量,都将直接从内存中获取最新值,而不是使用可能...

    volatile 变量的说明

    2. **禁止指令重排序**:编译器通常会对指令进行重排序以优化性能,但volatile变量的写操作后,后续读操作不能被提前,写操作前的读操作也不能被延后。这确保了对volatile变量的修改按照程序的顺序进行,防止出现...

    C中volatile_const解析

    ### C中`volatile`与`const`解析 #### 概述 在C语言中,`volatile`和`const`是两个非常重要的关键字,它们在不同的场景下有着独特的用途。掌握这两个关键字不仅能够帮助开发者写出更加高效和可靠的代码,还能够在...

    volatile使用详解

    2. **多任务环境**:在多任务环境中,各个任务之间共享的标志应该标记为 `volatile`,以确保线程间的同步和通信。 - 示例:如果多个任务需要通过共享变量来协调它们的行为,那么这些变量应该使用 `volatile` 来标记...

    volatile的使用

    2、多任务环境下各任务间共享的标志应该加volatile; 3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义; 另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几...

    volatile变量

    2. **volatile指针**:指针也可以被声明为`volatile`。例如,在中断服务子程序中修改指向缓冲区的指针时,可以将其声明为`volatile`以确保指针的值始终是最新的。 3. **使用volatile变量的潜在问题**:在使用`...

    volatile_unsigned_int

    2. 正确处理硬件事件:许多硬件操作不是通过程序直接控制的,而是由外部事件(如中断)来触发。如果没有volatile修饰符,编译器可能会错误地认为某个内存位置的值不会改变,从而导致程序行为出错。 3. 避免死循环:...

    java volatile 关键字实战

    java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java volatile 关键字实战java ...

Global site tag (gtag.js) - Google Analytics