Java中最简单易懂的volatile关键字示例
Posted
技术标签:
【中文标题】Java中最简单易懂的volatile关键字示例【英文标题】:Simplest and understandable example of volatile keyword in Java 【发布时间】:2013-07-18 20:33:26 【问题描述】:我正在阅读 Java 中的 volatile 关键字并完全理解它的理论部分。
但是,我正在寻找的是一个很好的例子,它显示了如果变量不是 volatile 会发生什么,如果它是。
下面的代码 sn-p 不能按预期工作(取自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.");
理想情况下,如果keepRunning
不是volatile,线程应该无限期地继续运行。但是,它确实会在几秒钟后停止。
我有两个基本问题:
谁能用例子解释一下volatile?不是 JLS 的理论。 volatile 是否可以替代同步?是否实现了原子性?【问题讨论】:
过去的帖子广泛讨论了它***.com/questions/7212155/java-threading-volatile 你在往回想。 理想情况下,如果 keepRunning 不是 volatile,线程应该无限期地继续运行。实际上,恰恰相反:添加volatile
保证 对字段的更改将是可见的。没有关键字,根本就没有任何保证,任何事情都可能发生;你不能说 thread 应该继续运行 [...].
事情是这样的:内存可见性错误本质上很难(不可能?)通过一个每次都会失败的简单示例来演示。假设您有一台多核机器,如果您经常运行它(例如,运行 1000 次),您的示例可能至少会失败几次。如果你有一个大程序——例如,整个程序及其对象不适合 CPU 缓存——那么就会增加看到错误的可能性。基本上,并发错误是这样的,如果理论上说它可以中断,它可能会,但每隔几个月只有一次,并且可能在生产中。
已经列出了很好的例子***.com/questions/5816790/…
这是一个例子,上面写着vanillajava.blogspot.co.uk/2012/01/…
【参考方案1】:
Volatile --> 保证可见性而不是原子性
同步(锁定)-->保证可见性和原子性(如果做得好)
易失性不能替代同步
只有在更新引用并且不对它执行一些其他操作时才使用 volatile。
例子:
volatile int i = 0;
public void incrementI()
i++;
如果不使用同步或 AtomicInteger 将不是线程安全的,因为递增是一种复合操作。
为什么程序不会无限期地运行?
这取决于各种情况。在大多数情况下,JVM 足够聪明,可以刷新内容。
Correct use of volatile 讨论了 volatile 的各种可能用途。正确使用 volatile 很棘手,我会说“如果有疑问,请不要使用它”,请改用同步块。
还有:
可以使用同步块代替 volatile,但反之则不然。
【讨论】:
这是错误的。 volatile 保证原子性质。 Oracle 文档明确规定了这一点。见docs.oracle.com/javase/tutorial/essential/concurrency/…。 在 Java 中,当我们有多个线程时,每个线程都有自己的堆栈(内存空间),并且 init 每个线程都有自己可以访问的变量副本。如果没有 volatile 关键字来装饰 int i ,则每个线程都可以在执行中使用它。当使用 volatile 声明时,每个线程必须直接从主内存读取/写入 i 的值,而不是从本地副本读取/写入。所以从每个线程的角度来看,对变量 i 的操作都是原子的。atomicity
部分答案令人困惑。同步为您提供互斥访问和可见性。 volatile
仅提供可见性。 volatile
也使 long
和 double
原子的读/写(同步也因其互斥性质而实现)。【参考方案2】:
对于您的特定示例:如果未声明为 volatile,则服务器 JVM 可以将 keepRunning
变量提升出循环,因为它未在循环中修改 in(将其变为无限循环),但客户端 JVM 不会。这就是为什么您会看到不同的结果。
关于 volatile 变量的一般解释如下:
当一个字段被声明为volatile
时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存操作重新排序。易失性变量不会缓存在寄存器或缓存中,它们对其他处理器是隐藏的,因此读取易失性变量总是返回任何线程最近的写入。
volatile 变量的可见性影响超出了 volatile 变量本身的值。当线程 A 写入 volatile 变量,随后线程 B 读取相同的变量时,在写入 volatile 变量之前对 A 可见的所有变量的值在读取 volatile 变量后对 B 可见。
volatile 变量最常见的用途是作为完成、中断或状态标志:
volatile boolean flag;
while (!flag)
// do something untill flag is true
易失性变量可用于其他类型的状态信息,但在尝试此操作时需要更加小心。例如,volatile 的语义不足以使增量操作 (count++
) 成为原子操作,除非您可以保证该变量仅从单个线程写入。
加锁可以同时保证可见性和原子性; volatile 变量只能保证可见性。
只有在满足以下所有条件时才能使用 volatile 变量:
对变量的写入不依赖于其当前值,或者您可以 确保只有一个线程更新该值; 该变量与其他状态变量不参与不变量;和 在访问变量时,出于任何其他原因不需要锁定。调试提示:确保在调用 JVM 时始终指定 -server
JVM 命令行开关,即使是用于开发和测试。服务端 JVM 比客户端 JVM 执行了更多的优化,比如将没有在循环中修改的变量提升到循环之外;可能在开发环境(客户端 JVM)中工作的代码可能会在部署环境中中断
(服务器 JVM)。
这是"Java Concurrency in Practice" 的摘录,这是你能找到的关于这个主题的最好的书。
【讨论】:
【参考方案3】:我稍微修改了你的例子。现在将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);
【讨论】:
很好的例子。这对我很有效。没有 volatile on keepRunning 线程将永远挂起。一旦您将 keepRunning 标记为 volatile - 它会在 t.keepRunning = false; 之后停止 示例为我工作,一直在寻找工作示例。 +1 因为它帮助了我,缺乏解释并没有伤害,也不值得投反对票。 嗨 paritosht 和@John Doe,您能帮忙解释一下为什么您的代码是一个工作示例吗?当我的机器执行问题中提供的代码时,不管有没有 volatile 关键字,它都会停止。 我在此处使用votalite
得到相同的结果【参考方案4】:
volatile
关键字是什么? volatile
关键字可防止缓存变量。
考虑这段代码,首先没有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();
理想情况下,这个程序应该打印hello
,直到按下Return键。但在某些机器上,变量running
可能会被缓存,您无法通过shutdown()
方法更改其值,这会导致hello
文本的无限打印。
因此,通过使用volatile
关键字,可以保证您的变量不会被缓存,并且代码在所有机器上都可以正常运行。
private volatile boolean running = true; //volatile keyword
使用volatile
关键字是一种良好且安全的编程习惯。
【讨论】:
“使用volatile
关键字是一种良好且安全的编程习惯。”听起来您应该明确地将其添加到所有变量中。如果您有其他方法来确保数据同步并且出于性能原因想要缓存,volatile
并不好或不安全。 volatile
是一个可以像其他任何东西一样被滥用的工具。【参考方案5】:
Variable Volatile
:Volatile 关键字适用于变量。 Java 中的 volatile 关键字保证 volatile 变量的值总是从主内存中读取,而不是从 Thread 的本地缓存中读取。
Access_Modifier volatile DataType Variable_Name;
易失性字段:向 VM 指示多个线程可能会尝试同时访问/更新该字段的值。一种特殊的实例变量,它必须在所有线程之间共享,具有修改的值。类似于静态(类)变量,只有一个易失性值的副本缓存在主存储器中,因此在执行任何 ALU 操作之前,每个线程必须在 ALU 操作之后从主存储器读取更新的值,它必须直接写入主存储器。 (对 volatile 变量 v 的写入与任何线程对 v 的所有后续读取同步)这意味着对 volatile 变量的更改始终对其他线程可见。
这里是nonvoltaile variable
如果线程 t1 更改了 t1 缓存中的值,线程 t2 无法访问更改的值,直到 t1 写入,t2 从主内存读取最近修改的值,这可能导致@987654335 @。
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: synchronized 适用于方法、块。允许在对象上一次只执行 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();
Static[
Class Field
] vs Volatile[Instance Field
] - 两者都没有被线程缓存静态字段对所有线程都是通用的,并存储在方法区中。 static 与 volatile 没用。静态字段不能序列化。
Volatile 主要与存储在堆区域中的实例变量一起使用。 volatile 的主要用途是维护所有线程的更新值。实例 volatile 字段可以是 Serialized。
@see
Volatile Vs Static in java System with multiple cores sharing an L2 cache JVM memory model【讨论】:
【参考方案6】:理想情况下,如果 keepRunning 不是 volatile,线程应该无限期地继续运行。但是,它确实会在几秒钟后停止。
如果您在单处理器中运行,或者您的系统非常繁忙,则操作系统可能会换出线程,这会导致某些级别的缓存失效。没有volatile
并不意味着内存将不共享,但 JVM 会出于性能原因尝试不同步内存,因此内存可能不会被更新。
另外需要注意的是System.out.println(...)
是同步的,因为底层PrintStream
进行同步以停止重叠输出。因此,您可以在主线程中“免费”获得内存同步。然而,这仍然不能解释为什么阅读循环会看到更新。
无论println(...)
行是输入还是输出,您的程序都会在配备 Intel i7 的 MacBook Pro 上的 Java6 下为我运行。
任何人都可以用例子解释 volatile 吗?不是 JLS 的理论。
我认为你的例子很好。不知道为什么它不适用于删除所有 System.out.println(...)
语句。它对我有用。
volatile 是同步的替代品吗?是否实现了原子性?
在内存同步方面,volatile
抛出与synchronized
块相同的内存屏障,除了volatile
屏障是单向的而不是双向的。 volatile
读取会引发负载屏障,而写入会引发存储屏障。 synchronized
块是添加了互斥锁的双向屏障。
然而,就atomicity
而言,答案是“视情况而定”。如果您正在从字段中读取或写入值,则 volatile
提供适当的原子性。但是,增加volatile
字段会受到++
实际上是3 个操作的限制:读取、递增、写入。在这种情况下或更复杂的互斥体情况下,可能需要一个完整的synchronized
块。 AtomicInteger
通过复杂的测试和设置自旋循环解决了 ++
问题。
【讨论】:
我评论了两个 SOPln 语句,但它仍然在几秒钟后停止..你能告诉我一个可以按预期工作的例子吗? 您是否在单处理器系统上运行@tm99?因为你的程序在 Macbook Pro Java6 上为我永远旋转。 我在 Win Xp 32 位 Java 6 上运行 “任何同步块(或任何易失性字段)都会导致所有内存同步”——你确定吗?你会提供 JLS 参考吗?据我所知,唯一的保证是在释放锁 L1 之前对内存进行的修改在线程获得 same 锁 L1 之后对它们是可见的;使用 volatiles,在 volatile 写入 F1 之前的所有内存修改在对 same 字段 F1 进行 volatile 读取之后对线程可见,这与说 all* 非常不同内存已同步。它不像任何线程运行同步块那么简单。 当 any 内存屏障被越过(synchronized
或 volatile
)时,all 内存存在“发生在之前”的关系。除非您锁定在同一监视器上,否则无法保证锁和同步的 顺序,这就是您所指的@BrunoReis。但如果println(...)
完成,则可以保证keepRunning
字段已更新。【参考方案7】:
当变量为volatile
时,保证不会被缓存,不同的线程会看到更新后的值。但是,不标记它volatile
并不能保证相反。 volatile
是那些在 JVM 中被破坏了很长时间但仍然没有被很好理解的东西之一。
【讨论】:
在现代多处理器@Jeff 中,您的最后一条评论有些错误/误导。 JVM 非常聪明地不刷新值,因为这样做会影响性能。 当 main 将 keepRunning 设置为 false 时,线程仍会看到更新,因为 JVM 很聪明地刷新值。但这并不能保证(请参阅上面@Gray 的评论)。【参考方案8】:volatile
不一定会产生巨大的变化,具体取决于 JVM 和编译器。但是,对于许多(边缘)情况,优化导致变量的更改未能被注意到与正确写入之间可能是不同的。
基本上,优化器可以选择将非易失性变量放在寄存器或堆栈上。如果另一个线程在堆或类的原语中更改它们,另一个线程将继续在堆栈上查找它,并且它会过时。
volatile
确保不会发生此类优化,并且所有读取和写入都直接到堆或所有线程都会看到的其他地方。
【讨论】:
【参考方案9】:很多很好的例子,但我只想补充一点,有很多场景需要volatile
,所以没有一个具体的例子来统治它们。
-
您可以使用
volatile
强制所有线程从主内存中获取变量的最新值。
您可以使用synchronization
来保护关键数据
您可以使用Lock
API
你可以使用Atomic
变量
查看更多Java volatile examples。
【讨论】:
【参考方案10】:请在下面找到解决方案,
这个变量的值永远不会被缓存在线程本地:所有的读取和写入都将直接进入“主内存”。 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;
请参考此链接http://java.dzone.com/articles/java-volatile-keyword-0 以获得更清晰的信息。
【讨论】:
虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。 是的,你完全正确。我会添加它。感谢您的宝贵意见。【参考方案11】:volatile关键字告诉JVM它可能被另一个线程修改。 每个线程都有自己的堆栈,因此它可以访问自己的变量副本。创建线程时,它会将所有可访问变量的值复制到自己的内存中。
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();
试试这个有和没有 volatile 的例子。
【讨论】:
【参考方案12】: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();
【讨论】:
【参考方案13】:声明为 volatile 的对象通常用于在线程之间传递状态信息,以确保 CPU 缓存被更新,即保持同步,在存在 volatile 字段的情况下,一条 CPU 指令,一个内存屏障,通常称为一个 membar 或栅栏,被发射以通过 volatile 字段值的更改来更新 CPU 缓存。
volatile 修饰符告诉编译器,被 volatile 修改的变量可能会被程序的其他部分意外更改。
volatile 变量只能在线程上下文中使用。看例子here
【讨论】:
缓存在现代 CPU 上始终保持同步,与 volatile 无关。以上是关于Java中最简单易懂的volatile关键字示例的主要内容,如果未能解决你的问题,请参考以下文章
java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)
java中volatile关键字,你真的了解吗?volatile原理剖析实例讲解(简单易懂)