2013-07-19 29 views
56

我念叨在Java中volatile关键字,并完全理解它的理论部分volatile关键字的理解的例子。简单,在java中

但是,我正在寻找的,一个很好的案例,这说明如果变量是不挥发会发生什么,如果它是。

低于预期(from aioobe

class Test extends Thread { 

    boolean keepRunning = true; 

    public void run() { 
     while (keepRunning) { 
     } 

     System.out.println("Thread terminated."); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     Test t = new Test(); 
     t.start(); 
     Thread.sleep(1000); 
     t.keepRunning = false; 
     System.out.println("keepRunning set to false."); 
    } 
} 

理想的代码片段无法正常工作,如果keepRunning是不挥发,线程应该继续indefinetely运行。但是,它在几秒钟后停止。

我有两个基本问题: -

  • 谁能解释挥发性与例子吗?不符合JLS的理论。
  • 是同步的易失性替代品吗?它是否实现原子性?
+0

它的一个过去的会谈后广泛http://stackoverflow.com/questions/7212155/ java-threading-volatile – AurA

+3

你正在思考倒退。 *理想情况下,如果keepRunning不是不稳定的,线程应该继续无限期地运行*。实际上,情况正好相反:添加volatile可以保证字段的变化是可见的。没有关键字,根本就没有保证,任何事情都可能发生;你不能说*线程应该继续运行[...] *。 –

+3

以下是这样的事情:内存可见性错误本身很难(不可能?)以演示一个简单的例子,每次都会失败。假设你有一台多核机器,如果你经常运行它(例如1000次运行),你的例子可能会失败至少几次。如果你有一个大的程序 - 例如整个程序和它的对象不适合CPU高速缓存,那么这会增加发现错误的可能性。基本上,并发性错误是这样的,如果理论说它可能会崩溃,它可能会,但每隔几个月就会一次,并且可能在生产中。 – yshavit

回答

1

理想情况下,如果keepRunning不是易失性的,线程应该保持无限期运行。但是,它在几秒钟后停止。

如果您正在单处理器中运行,或者如果您的系统非常繁忙,则操作系统可能会交换导致某些级别的缓存失效的线程。正如其他人所提到的,没有volatile并不意味着内存将而不是被共享,但JVM试图不会同步内存,如果它可以出于性能原因,所以内存可能不会更新。

要注意的另一件事是System.out.println(...)是同步的,因为底层PrintStream做同步以停止重叠输出。所以你在主线程中获得了“免费”的内存同步。这仍然不能解释为什么阅读循环看到所有更新。

无论println(...)线或缩小,你的程序的Java6下旋转,我在MacBook Pro上采用Intel酷睿i7。

谁能解释挥发性与例子吗?不符合JLS的理论。

我想你的榜样是好的。不知道为什么它不能与所有删除的System.out.println(...)语句一起使用。这个对我有用。

是同步波动的替代品?它是否实现原子性?

在存储器同步方面,volatile抛出了相同存储器障碍,不同之处在于volatile屏障是单向与双向一个​​块。 volatile读取抛出一个负载障碍,而写道抛出商店的障碍。 A​​区块是双向屏障。

然而就atomicity而言,答案是“取决于”。如果您正在读取或写入某个字段的值,则volatile会提供适当的原子性。但是,增加一个volatile字段受限于++实际上是3个操作的限制:读取,增量,写入。在这种情况下或更复杂的互斥体情况下,完整的​​块是按顺序排列的。

+0

我评论了两个SOPln语句,但它在几秒钟后仍然停止..你能告诉我一个例子吗?会按预期工作? – tmgr

+0

你在单个处理器系统@ tm99上运行吗?因为你的程序在Macbook Pro Java6上永远为我旋转。 – Gray

+0

我在Win XP 32位Java上运行6 – tmgr

1

volatile不一定会创造巨大的变化,这取决于JVM和编译器。然而,对于许多(边缘)情况,它可能是优化造成变量的变化无法被注意到而不是被正确写入的区别。

基本上,优化器可能会选择将非易失性变量放在寄存器或堆栈上。如果另一个线程在堆或类的原语中改变它们,另一个线程将继续在堆栈中查找它,并且它将失效。

volatile确保不会发生这样的优化,并且所有读写操作都直接到堆或其他所有线程都会看到它的位置。

4

当一个变量是volatile时,它保证它不会被缓存,并且不同的线程会看到更新的值。但是,不标记volatile并不能保证相互对立。 volatile是那些在JVM中被打破了很长时间的事情之一,但仍然不是很好理解。

+3

请用代码解释。我知道它的理论 – tmgr

+0

在一个现代化的多处理器@Jeff中,你最近的评论有些错误/误导。 JVM真的非常聪明,因为这样做是性能低下。 – Gray

+0

当keepRunning被main设置为false时,线程仍然会看到更新,因为JVM明智地清除该值。这并不能保证(见上面@Gray的评论)。 –

44

挥发性 - >担保的知名度和NOT原子

同步(锁定) - >担保的知名度和原子性(如果做得好)

挥发性并不是同步的替代

只有在更新引用并且不对其执行其他操作时才使用volatile。

实施例:

volatile int i = 0; 

public void incrementI(){ 
    i++; 
} 

不会线程不使用同步或的AtomicInteger的安全的,因为递增是复合操作。

为什么程序不能无限期地运行?

那么这取决于各种情况。在大多数情况下,JVM足够聪明地刷新内容。

Correct use of volatile讨论了volatile的各种可能的用法。正确使用volatile会比较棘手,我会说“如果有疑问,请不要使用它”,而是使用synchronized块。

另外:

同步块可代替的挥发性被使用,不过逆是不正确的

+1

这是错误的。挥发性保证原子性质。 Oracle文档明确指出了这一点。请参阅http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html。 –

+2

在Java中,当我们有多个线程时,每个线程都有自己的栈(一个内存空间),init每个线程都有自己可以访问的变量副本。如果volatile关键字不是用来装饰int i的话,那么每个线程都可以在它们的执行中使用它。当用volatile声明时,每个线程都必须从/向direcltly主内存读取/写入i值,而不是从本地副本读取/写入。所以在每个线程的角度来看,对变量i的操作是原子操作。 –

23

为您具体的例子:如果没有声明挥发性服务器JVM可以扯起keepRunning变量圈外的,因为它不是在修改环(把它变成一个无限循环),但客户端JVM不会。这就是为什么你看到不同的结果。

约volatile变量一般解释如下:

当某个字段声明volatile,编译器和运行时都穿上了通知,这个变量是共享的,并且它的操作不应该与其他内存操作进行重新排序。易失性变量不会缓存在寄存器或缓存中,因为它们与其他 处理器隐藏起来,所以读取易失性变量始终会返回任何线程的最新写入。

易失性变量的可见性影响超出了易失性变量本身的值。当线程A写入一个易失性变量,随后线程B读取同一个变量时,在写入易失性变量之前A对A可见的所有变量的值在读取易失性变量后对B可见

最常见对于使用volatile变量是作为完成,中断,或状态标志:

volatile boolean flag; 
    while (!flag) { 
    // do something untill flag is true 
    } 

volatile变量可用于其他类型的状态信息,但这个尝试时,需要更多的照顾。例如,volatile的语义不足以使增量操作(count++)为原子,除非可以保证该变量仅从单个线程写入。

锁定可以保证可见性和原子性; volatile变量只能保证可见性。

可以使用,只有当以下所有条件都满足volatile变量:

  • 写入变量,不取决于它的当前值,或者您也可以 确保只有一个线程不断更新值;
  • 该变量不参与与其他状态变量的不变量;和
  • 正在访问变量时,由于任何其他原因不需要锁定。

调试提示:一定调用JVM时,即使是用于开发和测试始终指定-server JVM命令行开关。服务器JVM执行比客户端JVM更多的优化,例如将变量从循环中提取出来,这些循环未在循环中修改;可能在开发环境(客户端JVM)中工作的代码可能会在部署环境 (服务器JVM)中中断。

这是摘录自Java Concurrency in Practice的书籍,您可以找到关于此主题的最佳书籍。

0

声明为volatile的对象通常用于在线程之间传递状态信息,为了确保CPU缓存更新,即在出现易失性字段,CPU指令,内存屏障等情况下保持同步被称为一个membar或栅栏,被发射来更新CPU缓存,其中一个volatile字段的值发生了变化。

volatile修饰符告诉编译器volatile修改的变量可以被程序的其他部分意外地改变。

volatile变量只能在线程上下文中使用。见示例here

12

我已经稍微修改了您的示例。现在,使用例如与keepRunning易失性和非易失性成员:

class TestVolatile extends Thread{ 
    //volatile 
    boolean keepRunning = true; 

    public void run() { 
     long count=0; 
     while (keepRunning) { 
      count++; 
     } 

     System.out.println("Thread terminated." + count); 
    } 

    public static void main(String[] args) throws InterruptedException { 
     TestVolatile t = new TestVolatile(); 
     t.start(); 
     Thread.sleep(1000); 
     System.out.println("after sleeping in main"); 
     t.keepRunning = false; 
     t.join(); 
     System.out.println("keepRunning set to " + t.keepRunning); 
    } 
} 
+3

-1没有解释会发生什么 – ComputerEngineer88

+0

很好的例子。这对我来说很完美。没有** volatile ** keepRunning线程永远挂起。一旦你将** keepRunning **标记为** volatile ** - 它会在** t.keepRunning = false后停止; ** – Boris

+3

示例为我工作,一直在寻找工作示例。 +1,因为它对我有帮助,缺乏解释并没有损害,也不值得投票。 –

1

请在下面找到解决方案,

这个变量的值将不会被线程本地缓存:所有读取和写入将直接进入到“主记忆”。线程每次更新原始变量时的挥发性强制。

public class VolatileDemo { 

    private static volatile int MY_INT = 0; 

    public static void main(String[] args) { 

     ChangeMaker changeMaker = new ChangeMaker(); 
     changeMaker.start(); 

     ChangeListener changeListener = new ChangeListener(); 
     changeListener.start(); 

    } 

    static class ChangeMaker extends Thread { 

     @Override 
     public void run() { 
      while (MY_INT < 5){ 
       System.out.println("Incrementing MY_INT "+ ++MY_INT); 
       try{ 
        Thread.sleep(1000); 
       }catch(InterruptedException exception) { 
        exception.printStackTrace(); 
       } 
      } 
     } 
    } 

    static class ChangeListener extends Thread { 

     int local_value = MY_INT; 

     @Override 
     public void run() { 
      while (MY_INT < 5){ 
       if(local_value!= MY_INT){ 
        System.out.println("Got Change for MY_INT "+ MY_INT); 
        local_value = MY_INT; 
       } 
      } 
     } 
    } 

} 

请参阅此链接http://java.dzone.com/articles/java-volatile-keyword-0以获得更多的清晰度。

+0

虽然这个链接可能回答这个问题,但最好在这里包含答案的基本部分并提供参考链接。如果链接页面更改,则仅链接答案可能会失效。 – admdrew

+0

是的,你是绝对正确的。我会添加它。感谢您的宝贵意见。 –

1

如果您声明变量为volatile,那么它将不会存储在本地缓存中。只要线程正在更新值,它就会更新到主内存。所以,其他线程可以访问更新的值。

6

什么是volatile关键字?

挥发性关键字防止caching of variables

考虑代码,第一不挥发性关键字

class MyThread extends Thread { 
    private boolean running = true; //non-volatile keyword 

    public void run() { 
     while (running) { 
      System.out.println("hello"); 
     } 
    } 

    public void shutdown() { 
     running = false; 
    } 
} 

public class Main { 

    public static void main(String[] args) { 
     MyThread obj = new MyThread(); 
     obj.start(); 

     Scanner input = new Scanner(System.in); 
     input.nextLine(); 
     obj.shutdown(); 
    }  
} 

理想,这个程序应该print hello直到RETURN key被按压。但在some machines上,可能发生变量运行cached,并且您无法从shutdown()方法更改其值,这会导致infinite打印hello文本。

因此,使用volatile关键字,它是guaranteed您的变量将不被缓存,即将run fineall machines

private volatile boolean running = true; //volatile keyword 

因此,使用挥发性关键字是goodsafer programming practice

2

Variable Volatile:易失性关键字适用于变量。 Java中的volatile关键字保证volatile变量的值总是从主内存中读取,而不是从Thread的本地缓存中读取。

Access_Modifier volatile DataType Variable_Name; 

易失场:指示VM多线程可能试图同时访问/更新字段的值。一个特殊类型的实例变量必须在所有线程中与Modified值共享。与静态(Class)变量类似,在主存储器中只缓存一个易失值副本,因此,在执行任何ALU操作之前,每个线程必须在ALU操作后从主存储器读取更新的值,才能将其写入主存储器direclty。 (写入volatile变量v与任何线程对v的所有后续读取进行同步)这意味着对其他线程始终可见对volatile变量的更改。

enter image description here

这里一个nonvoltaile variable如果线程T1变化T1的缓存值,线程T2不能访问更改后的值,直到T1写道,T2从主内存中读取最新修改后的值,这可能导致Data-Inconsistancy

volatile cannot be cached - assembler

+--------------+--------+-------------------------------------+ 
    | Flag Name | Value | Interpretation      | 
    +--------------+--------+-------------------------------------+ 
    | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.| 
    +--------------+--------+-------------------------------------+ 
    |ACC_TRANSIENT | 0x0080 | Declared transient; not written or | 
    |    |  | read by a persistent object manager.| 
    +--------------+--------+-------------------------------------+ 

Shared Variables: 存储器中,可以在线程之间共享被称为共享存储器或堆存储器。所有实例字段,静态字段和数组元素都存储在堆内存中。

Synchronization:同步适用于方法,块。允许一次只在对象上执行1线程。如果t1取得控制权,则剩余的线程必须等待直到它释放控制权。

实施例:

public class VolatileTest implements Runnable { 

    private static final int MegaBytes = 10241024; 

    private static final Object counterLock = new Object(); 
    private static int counter = 0; 
    private static volatile int counter1 = 0; 

    private volatile int counter2 = 0; 
    private int counter3 = 0; 

    @Override 
    public void run() { 
     for (int i = 0; i < 5; i++) { 
      concurrentMethodWrong(); 
     } 

    } 

    void addInstanceVolatile() { 
     synchronized (counterLock) { 
      counter2 = counter2 + 1; 
      System.out.println(Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2); 
     } 
    } 

    public void concurrentMethodWrong() { 
     counter = counter + 1; 
     System.out.println(Thread.currentThread().getName() +" « Static :: "+ counter); 
     sleepThread(1/4); 

     counter1 = counter1 + 1; 
     System.out.println(Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1); 
     sleepThread(1/4); 

     addInstanceVolatile(); 
     sleepThread(1/4); 

     counter3 = counter3 + 1; 
     sleepThread(1/4); 
     System.out.println(Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3); 
    } 
    public static void main(String[] args) throws InterruptedException { 
     Runtime runtime = Runtime.getRuntime(); 

     int availableProcessors = runtime.availableProcessors(); 
     System.out.println("availableProcessors :: "+availableProcessors); 
     System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory()/MegaBytes); 
     System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory()/MegaBytes); 
     System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory()/MegaBytes); 
     System.out.println(" ===== ----- ===== "); 

     VolatileTest volatileTest = new VolatileTest(); 
     Thread t1 = new Thread(volatileTest); 
     t1.start(); 

     Thread t2 = new Thread(volatileTest); 
     t2.start(); 

     Thread t3 = new Thread(volatileTest); 
     t3.start(); 

     Thread t4 = new Thread(volatileTest); 
     t4.start(); 

     Thread.sleep(10);; 

     Thread optimizeation = new Thread() { 
      @Override public void run() { 
       System.out.println("Thread Start."); 

       Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2; 

       System.out.println("End of Thread." + appendingVal); 
      } 
     }; 
     optimizeation.start(); 
    } 

    public void sleepThread(long sec) { 
     try { 
      Thread.sleep(sec * 1000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

静态[Class Field] VS挥发性[Instance Field] - 两者都没有被线程缓存

  • 静态字段是共同的线程并存储在方法区域中。静态与volatile不使用。静态字段不能被序列化。

  • 易失性主要用于存储在堆区的实例变量。 volatile的主要用途是保持所有线程的更新值。实例易失性字段可以是Serialized

@see