简体   繁体   English

Java 中 volatile 关键字的最简单易懂的例子

[英]Simplest and understandable example of volatile keyword in Java

I'm reading about volatile keyword in Java and completely understand the theory part of it.我正在阅读 Java 中的volatile关键字并完全理解其中的理论部分。

But, what I'm searching for is, a good case example, which shows what would happen if variable wasn't volatile and if it were.但是,我正在寻找的是一个很好的案例示例,它显示了如果变量不是volatile并且如果是,会发生什么。

Below code snippet doesn't work as expected (taken from here ):下面的代码片段无法按预期工作(取自此处):

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.");
    }
}

Ideally, if keepRunning wasn't volatile , thread should keep on running indefinitely.理想情况下,如果keepRunning不是volatile ,线程应该无限期地继续运行。 But, it does stop after few seconds.但是,它确实在几秒钟后停止。

I've got two basic questions:我有两个基本问题:

  • Can anyone explain volatile with example?任何人都可以用例子来解释 volatile 吗? Not with theory from JLS.不是 JLS 的理论。
  • Is volatile substitute for synchronization? volatile 是同步的替代品吗? Does it achieve atomicity?它实现了原子性吗?

Volatile --> Guarantees visibility and NOT atomicity 易挥发->确保可见性和非原子性

Synchronization (Locking) --> Guarantees visibility and atomicity (if done properly) 同步(锁定)->确保可见性和原子性(如果操作正确)

Volatile is not a substitute for synchronization 易失性不能替代同步

Use volatile only when you are updating the reference and not performing some other operations on it. 仅在更新参考并且不对其执行某些其他操作时,才使用volatile。

Example: 例:

volatile int i = 0;

public void incrementI(){
   i++;
}

will not be thread safe without use of synchronization or AtomicInteger as incrementing is an compound operation. 如果不使用同步或AtomicInteger,将不会是线程安全的,因为递增是复合操作。

Why program does not run indefinitely? 为什么程序不会无限期运行?

Well that depends on various circumstances. 好吧,这取决于各种情况。 In most cases JVM is smart enough to flush the contents. 在大多数情况下,JVM足够聪明来刷新内容。

Correct use of volatile discusses various possible uses of volatile. 正确使用volatile讨论了volatile的各种可能用法。 Using volatile correctly is tricky, I would say "When in doubt, Leave it out", use synchronized block instead. 正确使用volatile是很棘手的,我会说“有疑问时,请不要使用它”,而是使用同步块。

Also: 也:

synchronized block can be used in place of volatile but the inverse is not true . 可以使用同步块代替volatile,但取反是不正确的

For your particular example: if not declared volatile the server JVM could hoist the keepRunning variable out of the loop because it is not modified in the loop (turning it into an infinite loop), but the client JVM would not. 为了您的具体的例子:如果没有声明挥发性服务器JVM可以扯起keepRunning ,因为它不是循环修改变量圈外(把它变成一个无限循环),但客户端JVM不会。 That is why you see different results. 这就是为什么您看到不同的结果的原因。

General explanation about volatile variables follows: 有关易失性变量的一般说明如下:

When a field is declared volatile , the compiler and runtime are put on notice that this variable is shared and that operations on it should not be reordered with other memory operations. 当将一个字段声明为volatile时,将通知编译器和运行时该变量是共享的,并且对该变量的操作不应与其他内存操作重新排序。 Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread . 易失性变量不会缓存在寄存器中,也不会缓存在对其他处理器隐藏的缓存中,因此读取易失性变量始终会返回任何线程的最新写入

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. 易失性变量的可见性影响超出了易失性变量本身的值。 When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable. 当线程A写入易失性变量,然后线程B读取相同的变量时,在写入易失性变量之前A可见的所有变量的值在读取volatile变量后对B可见。

The most common use for volatile variables is as a completion, interruption, or status flag: volatile变量最常见的用途是作为完成,中断或状态标志:

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

Volatile variables can be used for other kinds of state information, but more care is required when attempting this. 易变变量可以用于其他类型的状态信息,但是在尝试这样做时需要格外小心。 For example, the semantics of volatile are not strong enough to make the increment operation ( count++ ) atomic, unless you can guarantee that the variable is written only from a single thread. 例如,volatile的语义不足以使增量操作( count++ )成为原子操作,除非您可以保证仅从单个线程写入变量。

Locking can guarantee both visibility and atomicity; 锁定可以保证可见性和原子性。 volatile variables can only guarantee visibility. volatile变量只能保证可见性。

You can use volatile variables only when all the following criteria are met: 仅当满足以下所有条件时,才能使用volatile变量:

  • Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value; 写入变量不取决于其当前值,或者您可以确保只有一个线程更新该值;
  • The variable does not participate in invariants with other state variables; 该变量不与其他状态变量一起参与不变式。 and
  • Locking is not required for any other reason while the variable is being accessed. 在访问变量时,由于其他任何原因都不需要锁定。

Debugging tip : be sure to always specify the -server JVM command line switch when invoking the JVM, even for development and testing. 调试技巧 :调用JVM时,即使用于开发和测试,也请务必始终指定-server JVM命令行开关。 The server JVM performs more optimization than the client JVM, such as hoisting variables out of a loop that are not modified in the loop; 服务器JVM比客户端JVM执行更多的优化,例如从循环中提升未在循环中修改的变量。 code that might appear to work in the development environment (client JVM) can break in the deployment environment (server JVM). 在开发环境(客户端JVM)中似乎有效的代码可能会在部署环境(服务器JVM)中中断。

This is an excerpt from "Java Concurrency in Practice" , the best book you can find on this subject. 这是“ Java Concurrency in Practice”的摘录,您可以找到有关此主题的最佳书籍。

I have modified your example slightly. 我已经稍微修改了您的示例。 Now use the example with keepRunning as volatile and non volatile member : 现在使用将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);
    }
}

What is volatile keyword ? 什么是volatile关键字?

volatile keyword prevents caching of variables . volatile关键字可防止caching of variables

Consider the code ,first without volatile keyword 考虑代码,首先没有volatile关键字

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();   
    }    
}

Ideally ,this program should print hello until RETURN key is pressed. 理想情况下 ,该程序应打个print hello直到按RETURN key But on some machines it may happen that variable running is cached and you cannot change its value from shutdown() method which results in infinite printing of hello text. 但是在some machines ,可能会发生以下情况: 运行中的变量被cached并且您无法通过shutdown()方法更改其值,这会导致infinite打出问候文本。

Thus using volatile keyword ,it is guaranteed that your variable will not be cached ,ie will run fine on all machines . 因此,使用volatile关键字,可以guaranteed您的变量不会被缓存,即可以在all machinesrun fine

private volatile boolean running = true;  //volatile keyword

Thus using volatile keyword is a good and safer programming practice . 因此,使用volatile关键字是一种good safer programming practice

Ideally, if keepRunning wasn't volatile, thread should keep on running indefinitely. 理想情况下,如果keepRunning不可变,则线程应无限期继续运行。 But, it does stop after few seconds. 但是,它会在几秒钟后停止。

If you are running in a single-processor or if your system is very busy, the OS may be swapping out the threads which causes some levels of cache invalidation. 如果您在单处理器中运行,或者系统非常繁忙,则操作系统可能会换出线程,从而导致某些级别的缓存失效。 Not having a volatile doesn't mean that memory will not be shared, but the JVM is trying to not synchronize memory if it can for performance reasons so the memory may not be updated. 没有volatile并不意味着将共享内存,但是由于性能原因,JVM会尝试不同步内存,因为这样可能不会更新内存。

Another thing to note is that System.out.println(...) is synchronized because the underlying PrintStream does synchronization to stop overlapping output. 还要注意的另一件事是System.out.println(...)已同步,因为基础PrintStream同步以停止重叠输出。 So you are getting memory synchronization "for free" in the main-thread. 因此,您可以在主线程中“免费”获得内存同步。 This still doesn't explain why the reading loop sees the updates at all however. 但是,这仍然不能解释为什么阅读循环完全看到更新。

Whether the println(...) lines are in or out, your program spins for me under Java6 on a MacBook Pro with an Intel i7. 无论是println(...)行是输入还是输出,在使用Intel i7的MacBook Pro上,您的程序都会在Java6下为我旋转。

Can anyone explain volatile with example ? 谁能用例子解释易失性? Not with theory from JLS. 不符合JLS的理论。

I think your example is good. 我认为您的榜样很好。 Not sure why it isn't working with all System.out.println(...) statements removed. 不知道为什么它不能与所有System.out.println(...)语句一起删除。 It works for me. 这个对我有用。

Is volatile substitute for synchronization ? 易失性可以代替同步吗? Does it achieve atomicity ? 它能达到原子性吗?

In terms of memory synchronization, volatile throws up the same memory barriers as a synchronized block except that the volatile barrier is uni-directional versus bi-directional. 在内存同步方面, volatile抛出与synchronized块相同的存储屏障,除了volatile屏障是单向的还是双向的。 volatile reads throw up a load-barrier while writes throw up a store-barrier. volatile读引发负载障碍,而写则引发存储障碍。 A synchronized block is a bi-directional barrier with the addition of mutex locking. synchronized块是带有互斥锁的双向屏障。

In terms of atomicity , however, the answer is "it depends". 然而,就atomicity而言,答案是“取决于”。 If you are reading or writing a value from a field then volatile provides proper atomicity. 如果要从字段读取或写入值,则volatile提供适当的原子性。 However, incrementing a volatile field suffers from the limitation that ++ is actually 3 operations: read, increment, write. 但是,增加一个volatile字段的局限性在于++实际上是3个操作:读取,递增,写入。 In that case or more complex mutex cases, a full synchronized block may be necessary. 在那种情况或更复杂的互斥情况下,可能需要一个完整的synchronized块。 AtomicInteger solves the ++ issue with a complicated test-and-set spin-loop. AtomicInteger通过复杂的测试和设置自旋循环解决了++问题。

Variable Volatile : Volatile Keyword is applicable to variables. Variable VolatileVariable Volatile关键字适用于变量。 volatile keyword in Java guarantees that value of the volatile variable will always be read from main memory and not from Thread's local cache. Java中的volatile关键字保证volatile变量的值始终从主内存中读取,而不是从Thread的本地缓存中读取。

Access_Modifier volatile DataType Variable_Name;

Volatile Field: An indication to the VM that multiple threads may try to access/update the field's value at the same time. 易失字段:向VM指示多个线程可能尝试同时访问/更新该字段的值。 To a special kind of instance variables which has to shared among all the threads with Modified value. 对于一种特殊的实例变量,必须在所有具有修改后值的线程之间共享。 Similar to Static(Class) variable, Only one copy of volatile value is cached in main memory, So that before doing any ALU Operations each thread has to read the updated value from Main memory after ALU operation it has to write to main memory direclty. 与Static(Class)变量类似,主内存中仅缓存了一个易失值副本,因此在执行任何ALU操作之前,每个线程必须在ALU操作之后从主内存中读取更新后的值,然后才必须写入主内存位置。 (A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread) This means that changes to a volatile variable are always visible to other threads. (对易失性变量v的写入将与任何线程对v的所有后续后续读取同步), 这意味着对易失性变量的更改始终对其他线程可见。

在此处输入图片说明

Here to a nonvoltaile variable if Thread t1 changes the value in t1's cache, Thread t2 can't access the changed value untill t1 writes, t2 read from main memory for the most recent modified value, which may lead to Data-Inconsistancy . 如果线程t1更改了t1的缓存中的值,那么这里是一个非nonvoltaile variable ,线程t2不能访问更改后的值,直到t1写入,t2从主存储器读取了最近的修改值,这可能导致Data-Inconsistancy

volatile cannot be cached - assembler volatile无法缓存 - 汇编器

  +--------------+--------+-------------------------------------+ | 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 : Memory that can be shared between threads is called shared memory or heap memory. Shared Variables :可以在线程之间共享的内存称为共享内存或堆内存。 All instance fields, static fields, and array elements are stored in heap memory. 所有实例字段,静态字段和数组元素都存储在堆内存中。

Synchronization : synchronized is applicable to methods, blocks. 同步 :同步适用于方法,块。 allows to execute only 1-thread at a time on object. 一次只能在对象上执行1个线程。 If t1 takes control, then remaining threads has to wait untill it release the control. 如果t1取得控制权,则其余线程必须等待直到它释放控制权。

Example: 例:

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();
        }
    }
}

Static[ Class Field ] vs Volatile[ Instance Field ] - Both are not cached by threads 静态[ Class Field ] vs挥发性[ Instance Field ]-两者都不被线程缓存

  • Static fields are common to all threads and get stored in Method Area. 静态字段是所有线程共有的,并存储在“方法区域”中。 Static with volatile no use. 静态与挥发性无用。 Static field cant be serialized. 静态字段无法序列化。

  • Volatile mainly used with instance variable which get stored in heap area. 易失性主要与实例变量一起使用,该实例变量存储在堆区域中。 The main use of volatile is to maintain updated value over all the Threads. volatile的主要用途是维护所有线程的更新值。 instance volatile field can be Serialized . 实例volatile字段可以序列化

@see @看到

When a variable is volatile , it is guaranteeing that it will not be cached and that different threads will see the updated value. 当变量为volatile ,可以保证不会缓存该变量,并且确保其他线程将看到更新后的值。 However, not marking it volatile does not guarantee the opposite. 但是,不将其标记为volatile并不能保证相反的效果。 volatile was one of those things that was broken in the JVM for a long time and still not always well understood. volatile是很长一段时间以来在JVM中中断的事情之一,但仍然不是很容易理解。

volatile is not going to necessarily create giant changes, depending on the JVM and compiler. volatile不一定会产生巨大的变化,具体取决于JVM和编译器。 However, for many (edge) cases, it can be the difference between optimization causing a variable's changes to fail to be noticed as opposed to them being correctly written. 但是,对于许多(边缘)情况,可能是优化之间的差异,这与导致变量更改没有被正确写入相反,导致变量的更改无法被注意到。

Basically, an optimizer may choose to put non-volatile variables on registers or on the stack. 基本上,优化器可以选择将非易失性变量放入寄存器或堆栈中。 If another thread changes them in the heap or the classes' primitives, the other thread will keep looking for it on the stack, and it'll be stale. 如果另一个线程在堆或类的原语中更改了它们,则另一个线程将继续在堆栈中寻找它,这将是过时的。

volatile ensures such optimizations don't happen and all reads and writes are directly to the heap or another place where all threads will see it. volatile确保不会发生这种优化,并且所有读写操作都直接在堆或所有线程都可以看到它的其他位置进行。

Please find the solution below, 请在下面找到解决方案,

The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory". 此变量的值永远不会在线程本地缓存:所有读取和写入操作都将直接进入“主内存”。 The volatile force the thread to update the original variable for each time. volatile强制线程每次都更新原始变量。

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;
                }
            }
        }
    }

}

Please refer this link http://java.dzone.com/articles/java-volatile-keyword-0 to get more clarity in it. 请参考此链接http://java.dzone.com/articles/java-volatile-keyword-0以获得更清晰的说明。

The volatile keyword tells the JVM that it may be modified by another thread. volatile关键字告诉JVM,它可以被另一个线程修改。 Each thread has its own stack, and so its own copy of variables it can access. 每个线程都有自己的堆栈,因此可以访问它自己的变量副本。 When a thread is created, it copies the value of all accessible variables in its own memory. 创建线程时,它将所有可访问变量的值复制到其自己的内存中。

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

try out this example with and without volatile. 使用和不使用volatile尝试该示例。

public class VolatileDemo {
    static class Processor {
        //without volatile program keeps running on my platform
        private boolean flag = false;

        public void setFlag() {
            System.out.println("setting flag true");
            this.flag = true;
        }

        public void process() {
            while(!flag) {
                int x = 5;
                // using sleep or sout will end the program without volatile.
                // Probably these operations, cause thread to be rescheduled, read from memory. Thus read new flag value and end.
            }

            System.out.println("Ending");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Processor processor = new Processor();
        Thread t1 = new Thread(processor::process);

        t1.start();

        Thread.sleep(2000);
        processor.setFlag();

    }
}

Objects that are declared as volatile are usually used to communicate state information among threads,To ensure CPU caches are updated, that is, kept in sync, in the presence of volatile fields, a CPU instruction, a memory barrier, often called a membar or fence, is emitted to update CPU caches with a change in a volatile field's value. 声明为易失性的对象通常用于在线程之间传递状态信息,以确保在存在易失性字段,CPU指令,内存屏障(通常称为“内存区”或“内存区”)时更新CPU缓存,即保持同步。发出篱笆,以通过更改易失字段的值来更新CPU缓存。

The volatile modifier tells the compiler that the variable modified by volatile can be changed unexpectedly by other parts of your program. volatile修饰符告诉编译器,由volatile修改的变量可能会被程序的其他部分意外更改。

The volatile variable must be used in Thread Context only. volatile变量必须仅在线程上下文中使用。 see the example here 看到这里的例子

Lot's of great examples, but I just want to add that there are a number of scenarios where volatile is required so there is no one concrete example to rule them a. 有很多很棒的例子,但我只是想补充一点,在很多情况下需要使用volatile ,因此没有一个具体的例子可以将它们排除在外。

  1. You can use volatile to force all threads to get latest value of the variable from main memory. 您可以使用volatile强制所有线程从主内存中获取变量的最新值。
  2. You can use synchronization to guard critical data 您可以使用synchronization来保护关键数据
  3. You can use Lock API 您可以使用Lock API
  4. You can use Atomic variables 您可以使用Atomic变量

Check it out for more Java volatile examples . 查看更多Java易失性示例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM