今天的工作中,我遇到了Java中的volatile
关键字。 不太熟悉,我发现了以下解释:
Java理论与实践:管理波动
鉴于该文章详细解释了所讨论的关键字,您是否曾经使用过它,或者是否曾见过可以正确使用该关键字的情况?
“…volatile修饰符确保读取字段的任何线程都将看到最新写入的值。” -Josh Bloch
如果您正在考虑使用volatile
,请阅读处理原子行为的java.util.concurrent
包。
Wikipedia上有关Singleton Pattern的帖子显示使用情况不稳定。
绝对没错。 (不仅在Java中,而且在C#中也是如此。)有时,您需要获取或设置一个值,该值必须保证是给定平台上的原子操作,例如int或boolean,但不需要线程锁定的开销。 volatile关键字使您可以确保在读取值时获得的是当前值,而不是刚被另一个线程的写入废弃的缓存值。
volatile
具有内存可见volatile
语义。 基本上,对所有读取器(尤其是其他线程)而言, volatile
字段的值在其上完成写操作之后变为可见。 没有volatile
,读者可以看到一些未更新的值。
要回答您的问题:是的,我使用一个volatile
变量来控制某些代码是否继续循环。 循环测试volatile
值,如果为true
继续。 可以通过调用“停止”方法将条件设置为false
。 在stop方法完成执行之后,该循环将看到false
并在测试该值时终止。
我极力推荐的“ 实践中的Java并发性 ”一书很好地解释了volatile
。 这本书是由撰写该问题中引用的IBM文章的同一人撰写的(实际上,他在该书的底部引用了他的书)。 我对volatile
使用是他的文章所说的“模式1状态标志”。
如果您想了解有关volatile
在幕后如何工作的更多信息,请阅读Java内存模型 。 如果您想超越该水平,请阅读Hennessy&Patterson这样的优秀计算机体系结构书籍,并阅读有关缓存一致性和缓存一致性的信息。
使用volatile
一个常见示例是使用volatile boolean
变量作为标志来终止线程。 如果您启动了一个线程,并且希望能够安全地从另一个线程中中断它,则可以让该线程定期检查一个标志。 要停止它,请将标志设置为true。 通过使标志为volatile
,可以确保正在检查它的线程在下次检查它时将看到它已被设置,而不必使用synchronized
块。
是的,每当您希望多线程访问一个可变变量时,都必须使用volatile。 这不是很常见的用例,因为通常您需要执行多个原子操作(例如,在修改变量之前检查变量状态),在这种情况下,您将使用同步块。
如果要开发多线程应用程序,则需要使用“ volatile”关键字或“ synchronized”关键字以及任何其他并发控制工具和技术。 此类应用程序的示例是桌面应用程序。
如果您正在开发将部署到应用程序服务器(Tomcat,JBoss AS,Glassfish等)的应用程序,则不必自己处理并发控制,因为应用程序服务器已经处理了并发控制。 实际上,如果我没记错的话,Java EE标准禁止在servlet和EJB中进行任何并发控制,因为它是“基础结构”层的一部分,您应该免于对其进行处理。 如果要实现单例对象,则只能在此类应用程序中进行并发控制。 如果您使用像Spring这样的框架来编织组件,这甚至已经解决了。
因此,在大多数Java开发中,其中应用程序是Web应用程序,并使用IoC框架(如Spring或EJB),则不需要使用“ volatile”。
是的,我经常使用它-它对多线程代码非常有用。 您所指的文章是一篇不错的文章。 尽管有两点要牢记:
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
}
}
请注意,无需同步
volatile
仅保证所有线程,甚至它们自己,都在递增。 例如:计数器同时看到变量的相同外观。 它不用于代替同步,原子或其他东西,而是完全使读取同步。 请不要将其与其他Java关键字进行比较。 如下面的示例所示,易失变量操作也是原子的,它们立即失败或成功。
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 list = new ArrayList();
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 list = new ArrayList();
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关键字进行原子操作。 @链接
关于volatile
要点:
synchronized
和volatile
和locks,可以在Java中进行synchronized
。 synchronized
变量。 将synchronized
关键字与变量一起使用是非法的,并且会导致编译错误。 可以在Java中使用volatile
变量,而不是在Java中使用synchronized
变量,该变量将指示JVM线程从主内存读取volatile
变量的值,而不是在本地对其进行缓存。 volatile
关键字。 资源
volatile
用法示例:
public class Singleton {
private static volatile Singleton _instance; // volatile variable
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null)
_instance = new Singleton();
}
}
return _instance;
}
}
在第一个请求到达时,我们正在懒惰地创建实例。
如果我们不使_instance
变量volatile
那么创建Singleton
实例的Singleton
将无法与其他线程通信。 因此,如果线程A正在创建Singleton实例,并且在创建后,CPU损坏等情况下,所有其他线程将无法看到_instance
的值不为null,他们将认为该实例仍被分配为null。
为什么会这样? 因为读取器线程未进行任何锁定,并且直到写入器线程从同步块中退出,否则内存将不同步,并且_instance
值也不会在主内存中更新。 使用Java中的Volatile关键字,此关键字由Java本身处理,并且此类更新将在所有阅读器线程中可见。
结论 :
volatile
关键字还用于在线程之间传递内存的内容。
无挥发物的使用示例:
public class Singleton{
private static Singleton _instance; //without volatile variable
public static Singleton getInstance(){
if(_instance == null){
synchronized(Singleton.class){
if(_instance == null) _instance = new Singleton();
}
}
return _instance;
}
上面的代码不是线程安全的。 尽管出于性能原因,它会在同步块中再次检查实例的值(出于性能原因),但是JIT编译器可以以在构造函数完成执行之前设置对实例的引用的方式重新排列字节码。 这意味着方法getInstance()返回的对象可能尚未完全初始化。 为了使代码具有线程安全性,从Java 5开始,可以将关键字volatile用于实例变量。 一旦对象的构造函数完全完成其执行,标记为volatile的变量仅对其他线程可见。
资源
Java中的volatile
用法 :
故障快速迭代器通常使用列表对象上的volatile
计数器来实现。
Iterator
,计数器的当前值将嵌入到Iterator
对象中。 Iterator
操作时,该方法将比较两个计数器值,如果两个计数器值不同,则抛出ConcurrentModificationException
。 故障安全迭代器的实现通常是轻量级的。 它们通常依赖于特定列表实现的数据结构的属性。 没有一般模式。
volatile键与变量一起使用时,将确保读取此变量的线程将看到相同的值。 现在,如果您有多个线程读取和写入变量,则使变量volatile不够,数据将被破坏。 映像线程读取了相同的值,但是每个线程都做了一些修改(例如增加了一个计数器),当写回内存时,数据完整性受到侵犯。 这就是为什么有必要使变量同步(可能有不同的方式)的原因
如果更改是通过1个线程完成的,而其他线程仅需要读取此值,则volatile将是合适的。
从oracle文档页面 ,需要volatile变量来解决内存一致性问题:
使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写操作都会与该变量的后续读取建立先发生后关系。
这意味着对volatile
变量的更改始终对其他线程可见。 这也意味着,当一个线程读取volatile变量,它看到不仅仅是最新变化的volatile
,而且代码的副作用,导致了变化。
正如Peter Parker
( Peter Parker
回答所解释的,在没有volatile
修饰符的情况下,每个线程的堆栈可能都有自己的变量副本。 通过将变量设置为volatile
,已解决了内存一致性问题。
请参阅jenkov教程页面,以更好地理解。
请查看相关的SE问题,以获取有关volatile和使用volatile的用例的更多详细信息:
Java中的volatile和Synchronized之间的区别
一个实际的用例:
您有很多线程,它们需要以特定格式打印当前时间,例如: java.text.SimpleDateFormat("HH-mm-ss")
。 Yon可以具有一个类,该类将当前时间转换为SimpleDateFormat
并每秒钟更新一次变量。 所有其他线程可以简单地使用此volatile变量在日志文件中打印当前时间。
易变变量是轻量级同步。 如果要求所有线程之间都具有最新数据可见性,并且原子性可能会受到影响,则在这种情况下,必须首选易变变量。 对易失性变量的读取总是返回任何线程进行的最新写入,因为它们既不缓存在寄存器中,也不缓存在其他处理器看不到的缓存中。 挥发物是无锁的。 当方案满足上述条件时,我将使用volatile。
volatile关键字有两种不同的用法。
防止JVM读取寄存器中的值,并强制从内存中读取其值。
繁忙标志用于防止线程在设备繁忙且该标志不受锁保护时继续进行:
while (busy) {
/* do something else */
}
当另一个线程关闭busy标志时,测试线程将继续:
busy = 0;
但是,由于在测试线程中经常访问busy,因此JVM可以通过将busy的值放在寄存器中来优化测试,然后测试寄存器的内容,而无需在每次测试之前读取内存中的busy值。 测试线程永远不会看到繁忙更改,而另一个线程只会更改内存中的繁忙值,从而导致死锁。 将忙碌标志声明为volatile会强制在每次测试之前读取其值。
降低内存一致性错误的风险。
使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写入都会与该变量的后续读取建立“先发生”关系。 这意味着对volatile变量的更改始终对其他线程可见。
没有内存一致性错误的读写技术称为原子动作 。
原子动作是一次有效地同时发生的动作。 原子动作不能停在中间:它要么完全发生,要么根本不发生。 直到动作完成,原子动作的副作用才可见。
您可以指定以下原子操作:
干杯!
使用volatile
关键字声明的变量具有两个主要特质,使其具有特殊性。
如果我们有一个volatile变量,则任何线程都无法将其缓存到计算机的(微处理器)缓存中。 访问总是从主存储器发生的。
如果对易失变量执行写操作 ,并且突然请求读操作 ,则可以保证写操作将在读操作之前完成 。
以上两个素质可以推论出
另一方面,
volatile
关键字是维护共享变量的理想方法,该共享变量具有n个读取器线程,只有一个写入器线程可以访问它。 一旦添加了volatile
关键字,就完成了。 没有关于线程安全性的任何其他开销。 相反,
我们不能仅仅使用volatile
关键字来满足一个共享变量,该共享变量具有多个写入线程来访问它 。
易失性确实在跟随。
1>不同线程对易失性变量的读写始终来自内存,而不是线程自己的缓存或cpu寄存器。 因此,每个线程始终处理最新值。 2>当2个不同的线程在堆中使用相同的实例或静态变量时,一个线程可能会看到其他线程的操作混乱。 参见jeremy manson的博客。 但是,挥发性在这里有帮助。
下面的代码完全运行,显示了许多线程如何可以按预定义的顺序执行并打印输出,而无需使用synced关键字。
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
的要求,该变量用于控制其他线程的线程执行(这是需要volatile
一种情况)。
// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
public static void main(String[] a) throws Exception {
Task task = new Task();
new Thread(task).start();
Thread.sleep(500);
long stoppedOn = System.nanoTime();
task.stop(); // -----> do this to stop the thread
System.out.println("Stopping on: " + stoppedOn);
}
}
class Task implements Runnable {
// Try running with and without 'volatile' here
private volatile boolean state = true;
private int i = 0;
public void stop() {
state = false;
}
@Override
public void run() {
while(state) {
i++;
}
System.out.println(i + "> Stopped on: " + System.nanoTime());
}
}
当不使用volatile
时:即使在“ Stopping on:xxx ”之后,您也永远不会看到“ Stopped on:xxx ”消息,并且程序将继续运行。
Stopping on: 1895303906650500
当使用volatile
:您将立即看到' Stopped on:xxx '。
Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300
演示: https : //repl.it/repls/SilverAgonizingObjectcode
volatile变量基本上在更新后基本上用于主共享缓存行中的即时更新(刷新),因此更改会立即反映到所有工作线程中。
它是管理硬件内存管理的一部分。 实际上,属性的值至少可以存储在RAM memory
或CPU cache memory
在多线程环境中,我们有一个shared resources
的概念,这是该领域的问题之一。 理论上,当您使用过时的资源时可能会遇到这种情况,因为不同的处理器使用不同的值。
Java具有保留volatile
关键字来应对这种情况。 它用于标记Java变量以直接和原子方式读写RAM内存 。
Java 5
扩展了称为责任happens-before guarantee
volatile
责任[About] 。 happens-before
每次后续读取同一字段happens-before
都会对volatile
字段进行写操作。
但是,当几个线程同时在volatile
字段中写入不同的值(即race condition
)时,情况又如何呢? synchronized
关键字是为了拯救[关于]
什么时候挥发足够?
当仅一个线程写入而其他线程仅读取volatile
保证时,它们将看到最新值
还请考虑使用volatile
变量是更昂贵的任务,它还可以防止指令重新排序(即使用技术happens-before
),这是正常的性能增强技术。 因此,仅在真正需要时才使用volatile
变量。
阅读詹科夫的解释