在今天的工作中,我在Java中遇到了volatile
关键字。我不太熟悉它,我发现了这个解释:
鉴于该文章解释了相关关键字的详细信息,您是否使用过它,或者您是否曾经看到过以正确方式使用此关键字的情况?
volatile
具有内存可见性的语义。基本上,在写操作完成后,volatile
字段的值对所有读者(特别是其他线程)都是可见的。没有volatile
,读者可以看到一些非更新的价值。
回答你的问题:是的,我使用volatile
变量来控制某些代码是否继续循环。循环测试volatile
值并继续,如果它是true
。可以通过调用“stop”方法将条件设置为false
。循环看到false
并在stop方法完成执行后测试值时终止。
我强烈推荐的书“Java Concurrency in Practice”给出了volatile
的一个很好的解释。本书由撰写问题中引用的IBM文章的同一人撰写(事实上,他引用了该文章底部的书)。我使用volatile
就是他的文章所称的“模式1状态标志”。
如果您想了解有关volatile
如何在幕后工作的更多信息,请阅读the Java memory model。如果你想要超越这个级别,请查看一本好的计算机体系结构书籍,如Hennessy & Patterson,并阅读缓存一致性和缓存一致性。
如果您正在开发多线程应用程序,则需要使用'volatile'关键字或'synchronized'以及您可能拥有的任何其他并发控制工具和技术。这种应用的示例是桌面应用。
如果您正在开发将部署到应用程序服务器(Tomcat,JBoss AS,Glassfish等)的应用程序,则您不必自己处理并发控制,因为应用程序服务器已经解决了这一问题。事实上,如果我记得正确,Java EE标准禁止在servlet和EJB中进行任何并发控制,因为它是“基础”层的一部分,你应该放弃它来处理它。如果要实现单例对象,则只在此类应用程序中执行并发控制。如果您使用像Spring这样的框架编织组件,这甚至已经解决了。
因此,在Java开发的大多数情况下,应用程序是Web应用程序并使用IoC框架(如Spring或EJB),您不需要使用“volatile”。
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<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);
}
}
}
是的,我使用它非常多 - 它对于多线程代码非常有用。你指出的文章很好。虽然有两个重要的事情需要牢记:
绝对没错。 (而且不只是在Java中,而且在C#中。)有时你需要获取或设置一个值,保证在你的给定平台上是一个原子操作,例如int或boolean,但不要求线程锁定的开销。 volatile关键字允许您确保在读取获取当前值的值时,而不是通过在另一个线程上写入而过时的缓存值。
访问volatile字段的每个线程将在继续之前读取其当前值,而不是(可能)使用缓存值。
只有成员变量可以是易失性的或瞬态的。
volatile关键字有两种不同的用法。
阻止JVM读取寄存器中的值,并强制从内存中读取其值。
忙标志用于防止线程在设备繁忙时继续,并且标志不受锁保护:
while (busy) {
/* do something else */
}
当另一个线程关闭忙标志时,测试线程将继续:
busy = 0;
但是,由于在测试线程中频繁访问繁忙,JVM可以通过将busy的值放在寄存器中来优化测试,然后在每次测试之前测试寄存器的内容而不读取内存中的busy值。测试线程永远不会看到繁忙的更改,而另一个线程只会更改内存中busy的值,从而导致死锁。将busy标志声明为volatile会强制在每次测试之前读取其值。
降低内存一致性错误的风险。
使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写入都会与后续读取同一个变量建立“之前发生”关系。这意味着对volatile变量的更改始终对其他线程可见。
没有内存一致性错误的读取,写入技术称为原子操作。
原子动作是一次有效发生的动作。原子动作不能在中间停止:它要么完全发生,要么根本不发生。在动作完成之前,原子动作的副作用是不可见的。
以下是您可以指定的原子操作:
干杯!
易失性变量是轻量级同步。当需要在所有线程中查看最新数据并且原子性可能受到损害时,在这种情况下,必须首选Volatile Variables。读取volatile变量总是返回由任何线程完成的最近写入,因为它们既不缓存在寄存器中也不缓存在其他处理器看不到的缓存中。易失性是无锁的。当场景符合上面提到的标准时,我使用volatile。
挥发性确实如下。
1>不同线程读取和写入volatile变量总是来自内存,而不是来自线程自己的缓存或cpu寄存器。所以每个线程总是处理最新的值。 2>当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
从oracle文档page开始,需要使用volatile变量来修复内存一致性问题:
使用volatile变量可降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系。
这意味着对volatile
变量的更改始终对其他线程可见。这也意味着当线程读取volatile变量时,它不仅会看到volatile
的最新更改,还会看到导致更改的代码的副作用。
正如在Peter Parker
回答中所解释的那样,在没有volatile
修饰符的情况下,每个线程的堆栈可能都有自己的变量副本。通过将变量设为volatile
,已修复了内存一致性问题。
请查看jenkov教程页面以便更好地理解。
查看相关的SE问题,了解有关使用volatile的volatile和用例的更多详细信息:
Difference between volatile and synchronized in Java
一个实际用例:
您有许多线程,需要以特定格式打印当前时间,例如:java.text.SimpleDateFormat("HH-mm-ss")
。 Yon可以有一个类,它将当前时间转换为SimpleDateFormat
并每隔一秒更新一次变量。所有其他线程可以简单地使用此volatile变量在日志文件中打印当前时间。
通过在Java应用程序中并发运行线程来异步修改Volatile变量。不允许具有与“主”存储器中当前保存的值不同的变量的本地副本。实际上,声明为volatile的变量必须使其数据在所有线程之间同步,这样无论何时在任何线程中访问或更新变量,所有其他线程都会立即看到相同的值。当然,易变量可能比“普通”变量具有更高的访问和更新开销,因为线程可以拥有自己的数据副本的原因是为了提高效率。
当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存操作重新排序。易失性变量不会缓存在寄存器或缓存中,而是隐藏在其他内存中处理器,因此读取volatile变量总是返回任何线程的最近写入。
供参考,请参考此http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html
“... volatile修饰符保证读取字段的任何线程都能看到最近写入的值。” - Josh Bloch
如果您正在考虑使用volatile
,请阅读处理原子行为的包java.util.concurrent
。
关于Singleton Pattern的维基百科帖子显示在使用中不稳定。
当与变量一起使用时,volatile键将确保读取此变量的线程将看到相同的值。现在,如果您有多个线程读取和写入变量,使变量volatile变得不够,数据将被破坏。图像线程已经读取了相同的值,但每个都已经完成了一些操作(比如增加了一个计数器),当写回内存时,违反了数据完整性。这就是为什么有必要使变量同步(不同的方式是可能的)
如果更改由1个线程完成,而其他人只需读取此值,则volatile将是合适的。
Java volatile关键字用于将Java变量标记为“存储在主存储器中”。更准确地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存读取,并且每次写入一个volatile变量都将写入主内存,而不仅仅是CPU缓存。
实际上,从Java 5开始,volatile关键字不仅仅保证将易失性变量写入主内存并从主内存中读取。
它是扩展的可视性保证所谓的事先保证。
挥发性的性能考虑因素
读取和写入volatile变量会导致变量被读取或写入主存储器。读取和写入主内存比访问CPU缓存更昂贵。访问volatile变量也会阻止指令重新排序,这是一种正常的性能增强技术。因此,当您确实需要强制实施变量可见性时,应该只使用volatile变量。
volatile变量基本上用于主共享缓存行中的即时更新(刷新),一旦更新,所以更改立即反映到所有工作线程。
下面是一个非常简单的代码,用于演示volatile
对变量的要求,该变量用于控制来自其他线程的Thread执行(这是需要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
但是:Kua zxsw指出
关于volatile
的重点:
synchronized
和volatile
以及锁可以实现Java中的同步。synchronized
变量。将synchronized
关键字与变量一起使用是非法的,将导致编译错误。您可以使用java synchronized
变量代替在Java中使用volatile
变量,该变量将指示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
实例的Thread无法与其他线程通信。因此,如果线程A正在创建Singleton实例,并且在创建之后,CPU就会破坏等,所有其他线程将无法将_instance
的值视为非null,并且他们将认为它仍然被赋值为null。
为什么会这样?因为读取器线程没有进行任何锁定,并且在写入器线程从同步块中出来之前,内存将不会同步,并且_instance
的值将不会在主内存中更新。使用Java中的Volatile关键字,这由Java本身处理,并且所有读取器线程都可以看到此类更新。
结论:
volatile
关键字也用于在线程之间传递内存的内容。
不使用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;
}
上面的代码不是线程安全的。虽然它在synchronized块中再次检查实例的值(出于性能原因),但JIT编译器可以重新排列字节码,方式是在构造函数完成执行之前设置对实例的引用。这意味着方法getInstance()返回一个可能尚未完全初始化的对象。为了使代码具有线程安全性,可以使用关键字volatile,因为Java 5用于实例变量。标记为volatile的变量只有在对象的构造函数完全执行后才能对其他线程可见。 Source
Java中的volatile
用法:
快速失败的迭代器通常使用列表对象上的volatile
计数器实现。
Iterator
时,计数器的当前值嵌入在Iterator
对象中。Iterator
操作时,该方法比较两个计数器值并且如果它们不同则抛出ConcurrentModificationException
。故障安全迭代器的实现通常是轻量级的。它们通常依赖于特定列表实现的数据结构的属性。没有一般模式。
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
的一个常见示例是使用volatile boolean
变量作为终止线程的标志。如果你已经启动了一个线程,并且你希望能够安全地从另一个线程中断它,你可以让线程定期检查一个标志。要停止它,请将标志设置为true。通过制作标志volatile
,您可以确保检查它的线程将在下次检查它时设置它,而不必使用synchronized
块。
使用volatile
关键字声明的变量具有两个使其特殊的主要特性。
以上两个品质推断出这一点
而另一方面,
volatile
关键字是维护共享变量的理想方式,该共享变量具有'n'个读线程并且只有一个写线程来访问它。一旦我们添加了volatile
关键字,就完成了。关于线程安全没有任何其他开销。反过来,
我们不能单独使用volatile
关键字来满足一个共享变量,该变量有多个写入线程访问它。
没有人提到长和双变量类型的读写操作的处理。读取和写入是参考变量和大多数原始变量的原子操作,但长变量和双变量类型除外,它们必须使用volatile关键字作为原子操作。 @link
是的,只要您希望多个线程访问可变变量,就必须使用volatile。它不是很常见的用例,因为通常需要执行多个原子操作(例如,在修改之前检查变量状态),在这种情况下,您将使用synchronized块。
在我看来,除了停止使用volatile关键字的线程之外的两个重要场景是: