您的位置:首页 > 编程语言 > Java开发

java线程技术4_Volatile

2013-03-01 21:25 309 查看
1.Volatile产生原因
      Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 

      Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。Volatile其实是告诉处理器,
不要将我放入工作内存, 请直接在主存操作我。

      volatile的覆盖范围仅仅变量级别的,它的同步代价很低.。Volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。 

2.Volatile不能使用场景
(1) volatile 变量不能用作线程安全计数器
      虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。(如果将值调整为只从单个线程写入,则可以使用)

public class TestRaceCondition { 

        private volatile int i = 0; 

      

        public void increase() { 

           i++; 

        } 

     

        public int getValue() { 

           return i; 

       } 

   }

当多线程执行increase方法时, 不能保证它的值会是线性递增的。

3.Volatile可以使用场景
      使用 volatile 变量替代锁,要始终牢记使用 volatile 的限制 
             —— 只有在状态真正独立于程序内其他内容时才能使用
volatile 

3.1状态标志
       volatile 布尔型变量作为状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”。

代码:将 volatile 变量作为状态标志使用

/**

 * 将 volatile 变量作为状态标志使用.

 *

 * @version V1.0 ,2011-4-2 

 * @author xiahui

 *

 */

public class VolatileThread1 extends Thread

{

     private int count=0;

     public static volatile boolean shutdownRequested=false;

     

     public void shutdown(){ 

         shutdownRequested = true; 

    }

     

    public void run(){

     while (!shutdownRequested){

     System.out.println(this.getName()+(++count));

     try{

     sleep(100);

     }catch(InterruptedException ex){

     }

     }

     }

     

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

   {

     VolatileThread1 counter1 = new VolatileThread1();

     VolatileThread1 counter2 = new VolatileThread1();

     VolatileThread1 counter3 = new VolatileThread1();

       

       counter1.setName("counter1_");

       counter2.setName("counter2_");

       counter3.setName("c3_");

       

       counter1.start();

       counter2.start();

       counter3.start();

       

       sleep(500); //
主线程延迟

       counter3.shutdown();

       System.out.println(Thread.currentThread().getName()+":shutdown");

   }

}

运行结果

counter1_1

counter2_1

c3_1

counter1_2

counter2_2

c3_2

counter1_3

counter2_3

c3_3

counter1_4

counter2_4

c3_4

counter1_5

counter2_5

c3_5

main:shutdown

注意:如果变量shutdownRequested不是静态的,则程序只能中止counter3线程,因为每个线程类的变量是私有的。

例2:该类用于

public class VolatileFlag {

    

    int x = 0;

    public volatile boolean v = false;

    public void writer() {

     x = 42;

     v = true;

     }

     public void reader() {

     if (v == true) {

           //uses x - guaranteed to see 42.

          System.out.println("
value="+x);

     }

     else

         System.out.println("value="+x);

     }

    }

运行类

public class VolatileThread2 extends Thread {

    private int count = 0;

    

    public VolatileFlag flag = null;

    public static volatile boolean shutdownRequested=false;

     

     public void shutdown(){ 

         shutdownRequested = true; 

    }

    public void run() {

        

        while (!shutdownRequested) {

            System.out.print(this.getName() + (++count));

            flag.reader();

            try {

                sleep(100);

            } catch (InterruptedException ex) {

            }

        }

    }

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

        

     VolatileFlag flag=new VolatileFlag();

     VolatileThread2 counter1 = new VolatileThread2();

     VolatileThread2 counter2 = new VolatileThread2();

     VolatileThread2 counter3 = new VolatileThread2();

     //三个线程共用一个对象

     counter1.flag=flag;

     counter2.flag=flag;

     counter3.flag=flag;

     

      counter1.setName("counter1_");

      counter2.setName("counter2_");

      counter3.setName("counter3_");

      

      counter1.start();

      counter2.start();

      counter3.start();

      

      sleep(3000); //
主线程延迟

      flag.writer();//主线程给出写操作

      sleep(200); //
主线程延迟

      counter3.shutdown();

      System.out.println(Thread.currentThread().getName()+":shutdown"+"
value="+flag.x);

  }

}

3.2开销较低的读-写锁策略
      如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。

public class CheesyCounter {

    private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {

        return value++;

    }

}

使用 synchronized 确保增量操作是原子的 ,使用 volatile 保证当前结果的可见性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: