今天工作时,我在Java中遇到了volatile关键字。不是很熟悉,我找到了这个解释。

鉴于那篇文章详细解释了所讨论的关键字,您曾经使用过它吗?或者您曾经看到过以正确方式使用该关键字的情况吗?


当前回答

假设一个线程修改了一个共享变量的值,如果你没有对该变量使用volatile修饰符的话。当其他线程想要读取这个变量的值时,它们看不到更新后的值,因为它们是从CPU的缓存而不是RAM内存中读取变量的值。这个问题也被称为能见度问题。

通过将共享变量声明为volatile,所有对计数器变量的写入都将立即写入主存。同样,所有对counter变量的读取都将直接从主存中读取。

public class SharedObject {
    public volatile int sharedVariable = 0;
}

对于非易失性变量,不能保证Java虚拟机(JVM)何时将数据从主存读取到CPU缓存,或何时将数据从CPU缓存写入主存。这可能会导致几个问题,我将在下面的部分中解释这些问题。


例子:

想象这样一种情况,两个或多个线程可以访问一个共享对象,该对象包含一个声明如下的计数器变量:

public class SharedObject {
    public int counter = 0;
}

再想象一下,只有线程1增加计数器变量,但是线程1和线程2都可以不时地读取计数器变量。

如果计数器变量没有被声明为volatile,则不能保证计数器变量的值何时从CPU缓存写入主存。这意味着CPU缓存中的计数器变量值可能与主存中的不相同。这种情况如下所示:

线程看不到一个变量的最新值,因为它还没有被另一个线程写回主存,这个问题被称为“可见性”问题。一个线程的更新对其他线程是不可见的。

其他回答

使用volatile的一个常见示例是使用volatile布尔变量作为终止线程的标志。如果您已经启动了一个线程,并且希望能够安全地从另一个线程中断它,您可以让线程定期检查标志。要阻止它,将标志设置为true。通过将标志设置为易失性,可以确保正在检查它的线程在下次检查时看到它已经设置好,甚至不必使用同步块。

…volatile修饰符保证任何读取字段的线程都能看到最近写入的值。——乔希·布洛赫 如果您正在考虑使用volatile,请仔细阅读java.util.concurrent包,它处理原子行为。 维基百科上关于单例模式的帖子显示了volatile的使用。

Volatile只保证所有线程,甚至线程本身,都是递增的。例如:计数器在同一时间看到变量的同一面。它不是用来代替同步或原子或其他东西,它完全使读取同步。请不要将其与其他java关键字进行比较。如下例所示,volatile变量操作也是原子性的,它们会立即失败或成功。

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

即使你放不放,结果也总会不一样。但是,如果您像下面那样使用AtomicInteger,结果将始终相同。同步也是如此。

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

Volatile执行以下操作。

不同线程对volatile变量的读写总是从内存,而不是从线程自己的缓存或cpu寄存器。所以每个线程总是处理最新的值。 2>当两个不同的线程在堆中使用相同的实例或静态变量时,其中一个线程可能会认为其他线程的操作是无序的。请看jeremy manson的博客。但不稳定在这里有所帮助。

下面完全运行的代码展示了如何在不使用synchronized关键字的情况下以预定义的顺序执行多个线程并打印输出。

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

为了实现这一点,我们可以使用以下完整的运行代码。

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

下面的github链接有一个自述,它给出了适当的解释。 https://github.com/sankar4git/volatile_thread_ordering

Volatile对于停止线程非常有用。

并不是说您应该编写自己的线程,Java 1.6有很多不错的线程池。但是如果你确定你需要一个线程,你需要知道如何停止它。

我使用的线程模式是:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

在上面的代码段中,while循环中读取close的线程与调用close()的线程不同。如果没有volatile,运行循环的线程可能永远看不到关闭的更改。

注意,这里不需要同步