Java中volatile和synchronized的区别

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3519664/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 01:29:21  来源:igfitidea点击:

Difference between volatile and synchronized in Java

javamultithreadingjava-mesynchronizedvolatile

提问by Albus Dumbledore

I am wondering at the difference between declaring a variable as volatileand always accessing the variable in a synchronized(this)block in Java?

我想知道在 Javavolatile中将变量声明为和始终在synchronized(this)块中访问变量之间的区别?

According to this article http://www.javamex.com/tutorials/synchronization_volatile.shtmlthere is a lot to be said and there are many differences but also some similarities.

根据这篇文章http://www.javamex.com/tutorials/synchronization_volatile.shtml有很多话要说,有很多不同,但也有一些相似之处。

I am particularly interested in this piece of info:

我对这条信息特别感兴趣:

...

  • access to a volatile variable never has the potential to block: we're only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
  • because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-writeas an atomic operation (unless we're prepared to "miss an update");

...

  • 对 volatile 变量的访问永远不会被阻塞:我们只进行简单的读或写,所以与同步块不同,我们永远不会持有任何锁;
  • 因为访问 volatile 变量永远不会持有锁,所以它不适合我们希望将读-更新-写作为原子操作的情况(除非我们准备“错过更新”);

What do they mean by read-update-write? Isn't a write also an update or do they simply mean that the updateis a write that depends on the read?

他们所说的read-update-write是什么意思?不是写也是更新还是他们只是意味着更新是依赖于读的写?

Most of all, when is it more suitable to declare variables volatilerather than access them through a synchronizedblock? Is it a good idea to use volatilefor variables that depend on input? For instance, there is a variable called renderthat is read through the rendering loop and set by a keypress event?

最重要的是,什么时候声明变量volatile而不是通过synchronized块访问它们更合适?volatile用于依赖于输入的变量是个好主意吗?例如,有一个称为render通过渲染循环读取并由按键事件设置的变量?

采纳答案by Lawrence Dol

It's important to understand that there are twoaspects to thread safety.

了解线程安全有两个方面很重要。

  1. execution control, and
  2. memory visibility
  1. 执行控制,和
  2. 内存可见性

The first has to do with controlling when code executes (including the order in which instructions are executed) and whether it can execute concurrently, and the second to do with when the effects in memory of what has been done are visible to other threads. Because each CPU has several levels of cache between it and main memory, threads running on different CPUs or cores can see "memory" differently at any given moment in time because threads are permitted to obtain and work on private copies of main memory.

第一个与控制代码何时执行(包括执行指令的顺序)以及它是否可以并发执行有关,第二个与其他线程何时可以看到内存中已完成操作的效果有关。因为每个 CPU 在它和主内存之间都有几个级别的缓存,运行在不同 CPU 或内核上的线程可以在任何给定的时间看到不同的“内存”,因为线程被允许获取和工作在主内存的私有副本上。

Using synchronizedprevents any other thread from obtaining the monitor (or lock) for the same object, thereby preventing all code blocks protected by synchronization on the same objectfrom executing concurrently. Synchronization alsocreates a "happens-before" memory barrier, causing a memory visibility constraint such that anything done up to the point some thread releases a lock appearsto another thread subsequently acquiring the same lockto have happened before it acquired the lock. In practical terms, on current hardware, this typically causes flushing of the CPU caches when a monitor is acquired and writes to main memory when it is released, both of which are (relatively) expensive.

Usingsynchronized可以防止任何其他线程获取同一对象的监视器(或锁),从而防止同一对象上受同步保护的所有代码块同时执行。同步创建了一个“之前发生”记忆障碍,引起内存可见性约束,使得任何事情到一些点线程释放锁出现另一个线程随后获取相同的锁定它获取锁定之前已经发生。实际上,在当前的硬件上,这通常会在获取监视器时刷新 CPU 缓存,并在释放时写入主内存,这两者(相对)都很昂贵。

Using volatile, on the other hand, forces all accesses (read or write) to the volatile variable to occur to main memory, effectively keeping the volatile variable out of CPU caches. This can be useful for some actions where it is simply required that visibility of the variable be correct and order of accesses is not important. Using volatilealso changes treatment of longand doubleto require accesses to them to be atomic; on some (older) hardware this might require locks, though not on modern 64 bit hardware. Under the new (JSR-133) memory model for Java 5+, the semantics of volatile have been strengthened to be almost as strong as synchronized with respect to memory visibility and instruction ordering (see http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile). For the purposes of visibility, each access to a volatile field acts like half a synchronization.

使用volatile,而另一方面,将强制所有访问(读或写)到易失性可变发生到主存储器,有效地把挥发性变量out CPU的高速缓存。这对于某些仅要求变量的可见性正确且访问顺序不重要的操作非常有用。使用volatile也改变了对它们的处理longdouble要求对它们的访问是原子的;在某些(较旧的)硬件上,这可能需要锁定,但在现代 64 位硬件上则不需要。在 Java 5+ 的新 (JSR-133) 内存模型下, volatile 的语义已得到加强,在内存可见性和指令排序方面几乎与同步一样强(参见http://www.cs.umd.edu /users/pugh/java/memoryModel/jsr-133-faq.html#volatile)。出于可见性的目的,对 volatile 字段的每次访问都相当于半个同步。

Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them. Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire. In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread Awhen it writes to volatile field fbecomes visible to thread Bwhen it reads f.

-- JSR 133 (Java Memory Model) FAQ

在新的内存模型下,volatile 变量不能相互重新排序仍然是正确的。不同之处在于现在不再那么容易对它们周围的正常字段访问重新排序。写入易失性字段与监视器释放具有相同的记忆效应,而从易失性字段读取与监视器获取具有相同的记忆效应。实际上,由于新的内存模型对 volatile 字段访问与其他字段访问(无论是否 volatile )的重新排序施加了更严格的限制,线程A在写入 volatile 字段f时可见的任何内容B在读取时对线程可见f

-- JSR 133(Java 内存模型)常见问题

So, now both forms of memory barrier (under the current JMM) cause an instruction re-ordering barrier which prevents the compiler or run-time from re-ordering instructions across the barrier. In the old JMM, volatile did not prevent re-ordering. This can be important, because apart from memory barriers the only limitation imposed is that, for any particular thread, the net effect of the code is the same as it would be if the instructions were executed in precisely the order in which they appear in the source.

因此,现在两种形式的内存屏障(在当前 JMM 下)都会导致指令重新排序屏障,从而阻止编译器或运行时跨屏障重新排序指令。在旧的 JMM 中, volatile 不会阻止重新排序。这很重要,因为除了内存屏障之外,唯一的限制是, 对于任何特定线程,代码的净效果与如果指令按照它们出现在来源。

One use of volatile is for a shared but immutable object is recreated on the fly, with many other threads taking a reference to the object at a particular point in their execution cycle. One needs the other threads to begin using the recreated object once it is published, but does not need the additional overhead of full synchronization and it's attendant contention and cache flushing.

volatile 的一种用途是动态重新创建共享但不可变的对象,许多其他线程在其执行周期的特定点引用该对象。一个需要其他线程在发布后开始使用重新创建的对象,但不需要完全同步的额外开销以及随之而来的争用和缓存刷新。

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

Speaking to your read-update-write question, specifically. Consider the following unsafe code:

说到你的读-更新-写问题,特别是。考虑以下不安全代码:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

Now, with the updateCounter() method unsynchronized, two threads may enter it at the same time. Among the many permutations of what could happen, one is that thread-1 does the test for counter==1000 and finds it true and is then suspended. Then thread-2 does the same test and also sees it true and is suspended. Then thread-1 resumes and sets counter to 0. Then thread-2 resumes and again sets counter to 0 because it missed the update from thread-1. This can also happen even if thread switching does not occur as I have described, but simply because two different cached copies of counter were present in two different CPU cores and the threads each ran on a separate core. For that matter, one thread could have counter at one value and the other could have counter at some entirely different value just because of caching.

现在,由于 updateCounter() 方法未同步,两个线程可能会同时进入它。在可能发生的许多排列中,一个是线程 1 对 counter==1000 进行测试并发现它为真,然后被挂起。然后线程 2 执行相同的测试,也看到它为真并被挂起。然后线程 1 恢复并将计数器设置为 0。然后线程 2 恢复并再次将计数器设置为 0,因为它错过了线程 1 的更新。即使线程切换没有像我所描述的那样发生,这也可能发生,但这仅仅是因为两个不同的计数器缓存副本存在于两个不同的 CPU 内核中,并且每个线程都在一个单独的内核上运行。就此而言,一个线程可能具有一个值的计数器,而另一个线程可能由于缓存而具有某个完全不同的值。

What's important in this example is that the variable counterwas read from main memory into cache, updated in cache and only written back to main memory at some indeterminate point later when a memory barrier occurred or when the cache memory was needed for something else. Making the counter volatileis insufficient for thread-safety of this code, because the test for the maximum and the assignments are discrete operations, including the increment which is a set of non-atomic read+increment+writemachine instructions, something like:

在这个例子中,重要的是变量counter从主内存读取到缓存中,在缓存中更新,并且仅在以后某个不确定的点当内存屏障发生或缓存内存需要用于其他东西时才写回主内存。制作计数器volatile对于这段代码的线程安全性是不够的,因为对最大值和赋值的测试是离散操作,包括增量是一组非原子read+increment+write机器指令,例如:

MOV EAX,counter
INC EAX
MOV counter,EAX

Volatile variables are useful only when alloperations performed on them are "atomic", such as my example where a reference to a fully formed object is only read or written (and, indeed, typically it's only written from a single point). Another example would be a volatile array reference backing a copy-on-write list, provided the array was only read by first taking a local copy of the reference to it.

仅当对它们执行的所有操作都是“原子的”时,易失性变量才有用,例如我的示例,其中对完全形成的对象的引用仅被读取或写入(实际上,通常它仅从一个点写入)。另一个示例是支持写时复制列表的易失性数组引用,前提是该数组仅通过首先获取对它的引用的本地副本来读取。

回答by Kerem Baydo?an

volatileis a field modifier, while synchronizedmodifies code blocksand methods. So we can specify three variations of a simple accessor using those two keywords:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()accesses the value currently stored in i1in the current thread. Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads.In particular, another thread may have updated i1in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a "main" memory, and this is the memory that holds the current "correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the "main" memory. So in fact, it is possible for the "main" memory to have a value of 1for i1, for thread1 to have a value of 2for i1and for thread2to have a value of 3for i1if thread1and thread2have both updated i1 but those updated value has not yet been propagated to "main" memory or other threads.

On the other hand, geti2()effectively accesses the value of i2from "main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in "main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Generally volatile variables have a higher access and update overhead than "plain" variables. Generally threads are allowed to have their own copy of data is for better efficiency.

There are two differences between volitile and synchronized.

Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block. That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with "main" memory. So executing geti3()does the following:

  1. The thread acquires the lock on the monitor for object this .
  2. The thread memory flushes all its variables, i.e. it has all of its variables effectively read from "main" memory .
  3. The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from "main" memory).
  4. (Any changes to variables would normally now be written out to "main" memory, but for geti3() we have no changes.)
  5. The thread releases the lock on the monitor for object this.

So where volatile only synchronizes the value of one variable between thread memory and "main" memory, synchronized synchronizes the value of all variables between thread memory and "main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

volatile是一个字段修饰符,而synchronized修改代码块方法。因此,我们可以使用这两个关键字指定简单访问器的三种变体:

    int i1;
    int geti1() {return i1;}

    volatile int i2;
    int geti2() {return i2;}

    int i3;
    synchronized int geti3() {return i3;}

geti1()访问当前存储在i1当前线程中的值。线程可以有变量的本地副本,并且数据不必与其他线程中保存的数据相同。特别是,另一个线程可能i1在其线程中进行了更新,但是当前线程中的值可能与其他线程中的值不同更新值。事实上,Java 有“主”内存的概念,这是保存变量当前“正确”值的内存。线程可以拥有自己的变量数据副本,并且线程副本可以与“主”内存不同。所以实际上,“主”内存的值有可能是1for i1,而 thread1 的值可能是2fori1和 for thread2如果线程 1线程 2都更新了 i1,但这些更新的值尚未传播到“主”内存或其他线程,则值为3i1

另一方面,geti2()有效地访问i2来自“主”内存的值。不允许 volatile 变量具有与当前保存在“主”内存中的值不同的变量的本地副本。实际上,声明为 volatile 的变量必须在所有线程之间同步其数据,这样无论何时您在任何线程中访问或更新该变量,所有其他线程都会立即看到相同的值。通常 volatile 变量比“普通”变量具有更高的访问和更新开销。通常允许线程拥有自己的数据副本是为了提高效率。

volitile 和 synchronized 之间有两个区别。

首先synchronized获取并释放监视器上的锁,一次只能强制一个线程执行一个代码块。这是众所周知的同步方面。但同步也同步内存。事实上,synchronized 将整个线程内存与“主”内存同步。因此,执行执行geti3()以下操作:

  1. 线程在监视器上获取对象 this 的锁。
  2. 线程内存刷新其所有变量,即它的所有变量都有效地从“主”内存中读取。
  3. 执行代码块(在这种情况下,将返回值设置为 i3 的当前值,该值可能刚刚从“主”内存中重置)。
  4. (对变量的任何更改现在通常都会写入“主”内存,但对于 geti3() 我们没有任何更改。)
  5. 该线程释放对象 this 的监视器上的锁。

所以其中 volatile 只同步线程内存和“主”内存之间的一个变量的值,synchronized 同步线程内存和“主”内存之间所有变量的值,并锁定和释放监视器以启动。显然,同步可能比 volatile 有更多的开销。

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

http://javaexp.blogspot.com/2007/12/difference-between-volatile-and.html

回答by Ravindra babu

synchronizedis method level/block level access restriction modifier. It will make sure that one thread owns the lock for critical section. Only the thread,which own a lock can enter synchronizedblock. If other threads are trying to access this critical section, they have to wait till current owner releases the lock.

synchronized是方法级/块级访问限制修饰符。它将确保一个线程拥有临界区的锁。只有拥有锁的线程才能进入synchronized块。如果其他线程试图访问这个临界区,他们必须等到当前所有者释放锁。

volatileis variable access modifier which forces all threads to get latest value of the variable from main memory. No locking is required to access volatilevariables. All threads can access volatile variable value at same time.

volatile是变量访问修饰符,它强制所有线程从主内存中获取变量的最新值。访问volatile变量不需要锁定。所有线程可以同时访问 volatile 变量值。

A good example to use volatile variable : Datevariable.

使用 volatile 变量的一个很好的例子:Date变量。

Assume that you have made Date variable volatile. All the threads, which access this variable always get latest data from main memory so that all threads show real (actual) Date value. You don't need different threads showing different time for same variable. All threads should show right Date value.

假设您已将 Date 设为变量volatile。访问此变量的所有线程始终从主内存中获取最新数据,以便所有线程显示真实(实际)日期值。您不需要不同的线程为同一变量显示不同的时间。所有线程都应显示正确的日期值。

enter image description here

在此处输入图片说明

Have a look at this articlefor better understanding of volatileconcept.

看看这篇文章,以更好地理解volatile概念。

Lawrence Dol cleary explained your read-write-update query.

Lawrence Dol cleary 解释了您的read-write-update query.

Regarding your other queries

关于您的其他查询

When is it more suitable to declare variables volatile than access them through synchronized?

什么时候声明变量 volatile 比通过同步访问它们更合适?

You have to use volatileif you think all threads should get actual value of the variable in real time like the example I have explained for Date variable.

你必须使用volatile,如果你认为所有的线程应该得到像我为日期变量解释的例子实时变量的实际值。

Is it a good idea to use volatile for variables that depend on input?

对依赖于输入的变量使用 volatile 是个好主意吗?

Answer will be same as in first query.

答案将与第一个查询中的相同。

Refer to this articlefor better understanding.

请参阅本文以获得更好的理解。

回答by David Refaeli

tl;dr:

tl;博士

There are 3 main issues with multithreading:

多线程有3个主要问题:

1) Race Conditions

1) 竞争条件

2) Caching / stale memory

2)缓存/陈旧内存

3) Complier and CPU optimisations

3) 编译器和 CPU 优化

volatilecan solve 2 & 3, but can't solve 1. synchronized/explicit locks can solve 1, 2 & 3.

volatile可以解 2 & 3,但不能解synchronized1。/explicit locks 可以解 1, 2 & 3。

Elaboration:

阐述

1) Consider this thread unsafe code:

1)考虑这个线程不安全的代码:

x++;

x++;

While it may look like one operation, it's actually 3: reading the current value of x from memory, adding 1 to it, and saving it back to memory. If few threads try to do it at the same time, the result of the operation is undefined. If xoriginally was 1, after 2 threads operating the code it may be 2 and it may be 3, depending on which thread completed which part of the operation before control was transferred to the other thread. This is a form of race condition.

虽然它看起来像一个操作,但实际上是 3 个操作:从内存中读取 x 的当前值,将其加 1,然后将其保存回内存。如果几个线程同时尝试执行此操作,则操作的结果是不确定的。如果x原来是1,在2个线程操作代码之后可能是2,也可能是3,这取决于哪个线程在控制转移到另一个线程之前完成了哪个部分的操作。这是竞争条件的一种形式。

Using synchronizedon a block of code makes it atomic- meaning it make it as if the 3 operations happen at once, and there's no way for another thread to come in the middle and interfere. So if xwas 1, and 2 threads try to preform x++we knowin the end it will be equal to 3. So it solves the race condition problem.

synchronized在代码块上使用使其具有原子性- 这意味着它就像 3 个操作同时发生一样,并且没有办法让另一个线程进入中间并进行干扰。所以如果x是 1,并且有 2 个线程尝试执行x++我们知道最终它会等于 3。所以它解决了竞争条件问题。

synchronized (this) {
   x++; // no problem now
}

Marking xas volatiledoes not make x++;atomic, so it doesn't solve this problem.

标记xvolatile不会使x++;原子化,因此它不能解决此问题。

2) In addition, threads have their own context - i.e. they can cache values from main memory. That means that a few threads can have copies of a variable, but they operate on their working copy without sharing the new state of the variable among other threads.

2) 此外,线程有自己的上下文——即它们可以缓存主内存中的值。这意味着一些线程可以拥有变量的副本,但它们在其工作副本上运行,而不会在其他线程之间共享变量的新状态。

Consider that on one thread, x = 10;. And somewhat later, in another thread, x = 20;. The change in value of xmight not appear in the first thread, because the other thread has saved the new value to its working memory, but hasn't copied it to the main memory. Or that it did copy it to the main memory, but the first thread hasn't updated its working copy. So if now the first thread checks if (x == 20)the answer will be false.

考虑在一个线程上,x = 10;. 稍后,在另一个线程中,x = 20;. 值的变化x可能不会出现在第一个线程中,因为另一个线程已将新值保存到其工作内存中,但尚未将其复制到主内存中。或者它确实将其复制到主内存,但第一个线程尚未更新其工作副本。因此,如果现在第一个线程检查if (x == 20)答案将是false.

Marking a variable as volatilebasically tells all threads to do read and write operations on main memory only. synchronizedtells every thread to go update their value from main memory when they enter the block, and flush the result back to main memory when they exit the block.

将变量标记为volatile基本上告诉所有线程仅在主内存上进行读写操作。synchronized告诉每个线程在进入块时从主内存更新它们的值,并在它们退出块时将结果刷新回主内存。

Note that unlike data races, stale memory is not so easy to (re)produce, as flushes to main memory occur anyway.

请注意,与数据竞争不同,陈旧内存并不容易(重新)产生,因为无论如何都会发生刷新到主内存。

3) The complier and CPU can (without any form of synchronization between threads) treat all code as single threaded. Meaning it can look at some code, that is very meaningful in a multithreading aspect, and treat it as if it's single threaded, where it's not so meaningful. So it can look at a code and decide, in sake of optimisation, to reorder it, or even remove parts of it completely, if it doesn't know that this code is designed to work on multiple threads.

3) 编译器和 CPU 可以(没有任何形式的线程间同步)将所有代码视为单线程。这意味着它可以查看一些代码,这在多线程方面非常有意义,并将其视为单线程,而它没有那么有意义。因此,它可以查看代码并决定,为了优化,对其重新排序,甚至完全删除其中的一部分,如果它不知道此代码旨在用于多线程。

Consider the following code:

考虑以下代码:

boolean b = false;
int x = 10;

void threadA() {
    x = 20;
    b = true;
}

void threadB() {
    if (b) {
        System.out.println(x);
    }
}

You would think that threadB could only print 20 (or not print anything at all if threadB if-check is executed before setting bto true), as bis set to true only after xis set to 20, but the compiler/CPU might decide to reorder threadA, in that case threadB could also print 10. Marking bas volatileensures that it won't be reordered (or discarded in certain cases). Which mean threadB could only print 20 (or nothing at all). Marking the methods as syncrhonized will achieve the same result. Also marking a variable as volatileonly ensures that it won't get reordered, but everything before/after it can still be reordered, so synchronization can be more suited in some scenarios.

您会认为 threadB 只能打印 20(或者如果在设置b为 true之前执行 threadB if-check 则根本不打印任何内容),因为b只有在x设置为 20后才设置为 true ,但编译器/CPU 可能会决定重新排序threadA,在这种情况下,threadB 也可以打印 10。标记bvolatile确保它不会被重新排序(或在某些情况下被丢弃)。这意味着 threadB 只能打印 20 个(或根本不打印)。将方法标记为同步将获得相同的结果。volatile还将变量标记为仅确保它不会被重新排序,但它之前/之后的所有内容仍然可以重新排序,因此在某些情况下同步可能更适合。

Note that before Java 5 New Memory Model, volatile didn't solve this issue.

请注意,在 Java 5 New Memory Model 之前, volatile 并没有解决这个问题。