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

tmg*_*mgr 71 java concurrency multithreading volatile

我正在阅读Java中的volatile关键字并完全理解它的理论部分.

但是,我正在寻找的是一个很好的案例,它展示了如果变量不是易变的话会发生什么.

下面的代码片段无法正常工作(来自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.");
    }
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,如果keepRunning不是volatile,则线程应该继续无限运行.但是,它会在几秒钟后停止.

我有两个基本问题: -

  • 任何人都可以用例子解释volatile吗?不是来自JLS的理论.
  • 是不稳定的替代同步?它是否实现了原子性?

Nar*_*hai 48

易失性 - >保证可见性而非原子性

同步(锁定) - >保证可见性和原子性(如果正确完成)

易失性不能代替同步

仅在更新引用且不对其执行某些其他操作时使用volatile.

例:

volatile int i = 0;

public void incrementI(){
   i++;
}
Run Code Online (Sandbox Code Playgroud)

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

为什么程序无法无限期运行?

那取决于各种情况.在大多数情况下,JVM足够智能来刷新内容.

正确使用volatile讨论了volatile的各种可能用途.正确使用volatile非常棘手,我会说"如果有疑问,请将其保留",请使用synchronized块.

也:

可以使用synchronized块代替volatile,但反之则不然.

  • 这是错的.挥发性保证原子性.Oracle文档明确指出了这一点.请参阅http://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html. (4认同)
  • 在Java中,当我们有多个线程时,每个线程都有自己的堆栈(一个内存空间),init每个线程都有自己可以访问的变量副本.如果没有易失性关键字来装饰int i,则每个线程可以在它们的执行中使用它.当使用volatile声明时,每个线程必须从/向direcltly主内存读取/写入i的值,而不是从本地副本读取/写入i的值.因此,在每个线程透视图中,对变量i的操作是原子的. (4认同)

Sok*_*lov 27

对于您的特定示例:如果未声明为volatile,则服务器JVM可以将keepRunning变量提升出循环,因为它未循环中修改(将其转换为无限循环),但客户端JVM不会.这就是你看到不同结果的原因.

关于易变量的一般解释如下:

声明字段时volatile,编译器和运行时会注意到此变量是共享的,并且不应对其中的操作与其他内存操作重新排序.易失性变量不会缓存在寄存器或缓存中,而是隐藏在其他处理器中,因此读取volatile变量始终会返回任何线程的最新写入.

volatile变量的可见性效果超出了volatile变量本身的值.当线程A写入volatile变量并且随后线程B读取同一个变量时,在写入volatile变量之前A可见的所有变量的值在读取volatile变量后变为B可见

volatile变量最常见的用途是完成,中断或状态标志:

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }
Run Code Online (Sandbox Code Playgroud)

易失性变量可用于其他类型的状态信息,但尝试此操作时需要更加小心.例如,volatile的语义不够强大,无法使increment(count++)原子化,除非您可以保证变量只从单个线程写入.

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

仅当满足以下所有条件时,才能使用volatile变量:

  • 对变量的写入不依赖于其当前值,或者您可以确保只有一个线程更新该值;
  • 变量不参与其他状态变量的不变量; 和
  • 在访问变量时,出于任何其他原因,不需要锁定.

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

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


小智 14

我稍微修改了你的例子.现在使用keepRunning作为volatile和non volatile成员的示例:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 示例对我有用,一直在寻找工作示例.+1因为它帮助了我,而且缺乏解释并没有伤害,也不值得投票. (4认同)
  • -1不解释会发生什么 (3认同)

Pra*_*shi 12

什么是volatile关键字?

volatile关键字阻止caching of variables.

考虑代码,首先没有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();   
    }    
}
Run Code Online (Sandbox Code Playgroud)

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

因此,使用volatile关键字,guaranteed您的变量将不会被缓存,即将run fine打开all machines.

private volatile boolean running = true;  //volatile keyword
Run Code Online (Sandbox Code Playgroud)

因此使用volatile关键字是goodsafer programming practice.


Gra*_*ray 6

理想情况下,如果keepRunning不可变,则线程应无限期继续运行。但是,它会在几秒钟后停止。

如果您在单处理器中运行,或者系统非常忙,则操作系统可能会换出线程,从而导致某些级别的缓存失效。没有a volatile并不意味着将不会共享内存,但是JVM出于性能原因会尝试不同步内存(如果可以的话),因此可能不会更新内存。

要注意的另一件事System.out.println(...)是同步,因为基础进行PrintStream同步以停止重叠的输出。因此,您可以在主线程中“免费”获得内存同步。但是,这仍然不能解释为什么阅读循环完全看到更新。

无论println(...)是输入输出还是输出输出,您的程序都可以在配备Intel i7的MacBook Pro上以Java6操作系统为我旋转。

谁能用例子解释易失性?不符合JLS的理论。

我认为您的榜样很好。不知道为什么它不能System.out.println(...)删除所有语句。这个对我有用。

易失性可以代替同步吗?它能达到原子性吗?

在内存同步方面,volatile抛出的内存屏障与synchronized块相同,除了volatile屏障是单向的还是双向的。 volatile读引发负载障碍,写引发存储障碍。阿synchronized块是双向屏障通过加入互斥锁定。

在方面atomicity,然而,答案是“看情况”。如果要从字段读取或写入值,则volatile可以提供适当的原子性。但是,增加volatile字段的限制++实际上是3个操作:读取,递增,写入。在那种情况或更复杂的互斥情况下,synchronized可能需要一个完整的块。 AtomicInteger通过++复杂的测试和设置自旋环解决了该问题。

  • “任何同步块(或任何易失性字段)都会导致所有内存同步”-您确定吗?您会为此提供JLS参考吗?据我所知,唯一的保证是,在线程获得“相同”锁L1之后,对释放线程L1之前执行的内存修改是可见的。对于volatile,对same字段F1进行易失性读取之后,线程对F1进行易失性写入之前的所有内存修改都是可见的,这与说“所有”内存已同步非常不同。它不像运行同步块的任何线程那么简单。 (2认同)

Yas*_*ash 6

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

Access_Modifier volatile DataType Variable_Name;
Run Code Online (Sandbox Code Playgroud)

易失字段:向VM指示多个线程可能尝试同时访问/更新该字段的值。对于一种特殊的实例变量,必须在所有具有修改后值的线程之间共享。与Static(Class)变量类似,主内存中仅缓存了一个易失值副本,因此在执行任何ALU操作之前,每个线程必须在ALU操作之后从主内存中读取更新后的值,然后才必须写入主内存位置。(对易失性变量v的写入将与任何线程对v的所有后续后续读取进行同步),这意味着对易失性变量的更改始终对其他线程可见。

在此处输入图片说明

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

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.|
    +--------------+--------+-------------------------------------+
Run Code Online (Sandbox Code Playgroud)

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

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

例:

Access_Modifier volatile DataType Variable_Name;
Run Code Online (Sandbox Code Playgroud)

静态[ Class Field] vs挥发性[ Instance Field]-两者都不被线程缓存

  • 静态字段是所有线程共有的,并存储在“方法区域”中。静态与挥发性无用。静态字段无法序列化。

  • 易失性主要与实例变量一起使用,该实例变量存储在堆区域中。volatile的主要用途是维护所有线程的更新值。实例volatile字段可以序列化

@看到