Java 何时使用 volatile 和 synchronized
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9851133/
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
When to use volatile and synchronized
提问by Stripies
I know there are many questions about this, but I still don't quite understand. I know what both of these keywords do, but I can't determine which to use in certain scenarios. Here are a couple of examples that I'm trying to determine which is the best to use.
我知道这方面有很多问题,但我还是不太明白。我知道这两个关键字的作用,但我无法确定在某些情况下使用哪个。这里有几个例子,我试图确定哪个是最好的。
Example 1:
示例 1:
import java.net.ServerSocket;
public class Something extends Thread {
private ServerSocket serverSocket;
public void run() {
while (true) {
if (serverSocket.isClosed()) {
...
} else { //Should this block use synchronized (serverSocket)?
//Do stuff with serverSocket
}
}
}
public ServerSocket getServerSocket() {
return serverSocket;
}
}
public class SomethingElse {
Something something = new Something();
public void doSomething() {
something.getServerSocket().close();
}
}
Example 2:
示例 2:
public class Server {
private int port;//Should it be volatile or the threads accessing it use synchronized (server)?
//getPort() and setPort(int) are accessed from multiple threads
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
Any help is greatly appreciated.
任何帮助是极大的赞赏。
回答by Péter T?r?k
Note: In your first example, the field serverSocket
is actually never initialized in the code you show.
注意:在您的第一个示例中,该字段serverSocket
实际上从未在您显示的代码中初始化。
Regarding synchronization, it depends on whether or not the ServerSocket
class is thread safe. (I assume it is, but I have never used it.) If it is, you don't need to synchronize around it.
关于同步,它取决于ServerSocket
类是否是线程安全的。(我假设它是,但我从未使用过它。)如果是,则不需要围绕它进行同步。
In the second example, int
variables can be atomically updated so volatile
may suffice.
在第二个示例中,int
变量可以原子更新,因此volatile
可能就足够了。
回答by Stephen C
A simple answer is as follows:
一个简单的答案如下:
synchronized
can always be used to give you a thread-safe / correct solution,volatile
will probably be faster, but can only be used to give you a thread-safe / correct in limited situations.
synchronized
总是可以用来给你一个线程安全/正确的解决方案,volatile
可能会更快,但只能用于在有限的情况下为您提供线程安全/正确。
If in doubt, use synchronized
. Correctness is more important than performance.
如果有疑问,请使用synchronized
. 正确性比性能更重要。
Characterizing the situations under which volatile
can be used safely involves determining whether each update operation can be performed as a single atomic update to a single volatile variable. If the operation involves accessing other (non-final) state or updating more than one shared variable, it cannot be done safely with just volatile. You also need to remember that:
表征volatile
可以安全使用的情况涉及确定每个更新操作是否可以作为对单个易失性变量的单个原子更新来执行。如果操作涉及访问其他(非最终)状态或更新多个共享变量,则仅使用 volatile 无法安全完成。您还需要记住:
- updates to non-volatile
long
or adouble
may not be atomic, and - Java operators like
++
and+=
are not atomic.
- 对非易失性
long
或 a 的更新double
可能不是原子的,并且 - Java 运算符喜欢
++
并且+=
不是原子的。
Terminology: an operation is "atomic" if the operation either happens entirely, or it does not happen at all. The term "indivisible" is a synonym.
术语:如果操作完全发生或根本不发生,则操作是“原子的”。术语“不可分割”是同义词。
When we talk about atomicity, we usuallymean atomicity from the perspective of an outside observer; e.g. a different thread to the one that is performing the operation. For instance, ++
is not atomic from the perspective of another thread, because that thread may be able to observe state of the field being incremented in the middle of the operation. Indeed, if the field is a long
or a double
, it may even be possible to observe a state that is neither the initial state or the final state!
当我们谈论原子性时,我们通常指的是从外部观察者的角度来看的原子性;例如,与正在执行操作的线程不同的线程。例如,++
从另一个线程的角度来看不是原子的,因为该线程可能能够观察到在操作中间增加的字段的状态。事实上,如果字段是 along
或 a double
,甚至可能观察到既不是初始状态也不是最终状态的状态!
回答by Adam Liss
The synchronized
keyword
该synchronized
关键字
synchronized
indicates that a variable will be shared among several threads. It's used to ensure consistency by "locking" access to the variable, so that one thread can't modify it while another is using it.
synchronized
表示一个变量将在多个线程之间共享。它用于通过“锁定”对变量的访问来确保一致性,以便一个线程无法在另一个线程使用它时修改它。
Classic Example: updating a global variable that indicates the current time
The incrementSeconds()
function must be able to complete uninterrupted because, as it runs, it creates temporary inconsistencies in the value of the global variable time
. Without synchronization, another function might see a time
of "12:60:00" or, at the comment marked with >>>
, it would see "11:00:00" when the time is really "12:00:00" because the hours haven't incremented yet.
经典示例:更新指示当前时间
的全局变量该incrementSeconds()
函数必须能够不间断地完成,因为在运行时,它会在全局变量的值中造成暂时的不一致time
。如果没有同步,另一个函数可能会看到time
"12:60:00" 或者,在标记>>>
为 "12:00:00"的注释中,当时间真的是 "12:00:00" 时,它会看到 "11:00:00",因为小时没有还没有增加。
void incrementSeconds() {
if (++time.seconds > 59) { // time might be 1:00:60
time.seconds = 0; // time is invalid here: minutes are wrong
if (++time.minutes > 59) { // time might be 1:60:00
time.minutes = 0; // >>> time is invalid here: hours are wrong
if (++time.hours > 23) { // time might be 24:00:00
time.hours = 0;
}
}
}
The volatile
keyword
该volatile
关键字
volatile
simply tells the compiler not to make assumptions about the constant-ness of a variable, because it may change when the compiler wouldn't normally expect it. For example, the software in a digital thermostat might have a variable that indicates the temperature, and whose value is updated directly by the hardware. It may change in places that a normal variable wouldn't.
volatile
只是告诉编译器不要对变量的常量性做出假设,因为当编译器通常不期望它时它可能会改变。例如,数字恒温器中的软件可能有一个指示温度的变量,其值由硬件直接更新。它可能会在正常变量不会改变的地方发生变化。
If degreesCelsius
is not declared to be volatile
, the compiler is free to optimize this:
如果degreesCelsius
未声明为volatile
,则编译器可以自由优化:
void controlHeater() {
while ((degreesCelsius * 9.0/5.0 + 32) < COMFY_TEMP_IN_FAHRENHEIT) {
setHeater(ON);
sleep(10);
}
}
into this:
进入这个:
void controlHeater() {
float tempInFahrenheit = degreesCelsius * 9.0/5.0 + 32;
while (tempInFahrenheit < COMFY_TEMP_IN_FAHRENHEIT) {
setHeater(ON);
sleep(10);
}
}
By declaring degreesCelsius
to be volatile
, you're telling the compiler that it has to check its value each time it runs through the loop.
通过声明degreesCelsius
为volatile
,您告诉编译器每次运行循环时都必须检查其值。
Summary
概括
In short, synchronized
lets you control access to a variable, so you can guarantee that updates are atomic (that is, a set of changes will be applied as a unit; no other thread can access the variable when it's half-updated). You can use it to ensure consistency of your data. On the other hand, volatile
is an admission that the contents of a variable are beyond your control, so the code must assume it can change at any time.
简而言之,synchronized
允许您控制对变量的访问,因此您可以保证更新是原子的(即,一组更改将作为一个单元应用;当变量更新到一半时,其他线程无法访问该变量)。您可以使用它来确保数据的一致性。另一方面,volatile
承认变量的内容超出了您的控制范围,因此代码必须假设它可以随时更改。
回答by Tudor
There is insufficient information in your post to determine what is going on, which is why all the advice you are getting is general information about volatile
and synchronized
.
您的帖子中没有足够的信息来确定发生了什么,这就是为什么您获得的所有建议都是关于volatile
和 的一般信息synchronized
。
So, here's my general advice:
所以,这是我的一般建议:
During the cycle of writing-compiling-running a program, there are two optimization points:
在编写-编译-运行程序的循环中,有两个优化点:
- at compile time, when the compiler might try to reorder instructions or optimize data caching.
- at runtime, when the CPU has its own optimizations, like caching and out-of-order execution.
- 在编译时,编译器可能会尝试重新排序指令或优化数据缓存。
- 在运行时,当 CPU 有自己的优化时,比如缓存和乱序执行。
All this means that instructions will most likely not execute in the order that you wrote them, regardless if this order must be maintained in order to ensure program correctness in a multithreaded environment. A classic example you will often find in the literature is this:
所有这些都意味着指令很可能不会按照您编写它们的顺序执行,无论是否必须保持此顺序以确保多线程环境中的程序正确性。您经常会在文献中找到的一个经典示例是:
class ThreadTask implements Runnable {
private boolean stop = false;
private boolean work;
public void run() {
while(!stop) {
work = !work; // simulate some work
}
}
public void stopWork() {
stop = true; // signal thread to stop
}
public static void main(String[] args) {
ThreadTask task = new ThreadTask();
Thread t = new Thread(task);
t.start();
Thread.sleep(1000);
task.stopWork();
t.join();
}
}
Depending on compiler optimizations and CPU architecture, the above code may never terminate on a multi-processor system. This is because the value of stop
will be cached in a register of the CPU running thread t
, such that the thread will never again read the value from main memory, even thought the main thread has updated it in the meantime.
根据编译器优化和 CPU 架构,上述代码可能永远不会在多处理器系统上终止。这是因为 的值stop
将被缓存在 CPU 运行线程的寄存器中t
,这样线程将永远不会再次从主内存中读取该值,即使主线程在此期间更新了它。
To combat this kind of situation, memory fenceswere introduced. These are special instructions that do not allow regular instructions before the fence to be reordered with instructions after the fence. One such mechanism is the volatile
keyword. Variables marked volatile
are not optimized by the compiler/CPU and will always be written/read directly to/from main memory. In short, volatile
ensures visibility of a variable's value across CPU cores.
为了应对这种情况,引入了内存栅栏。这些是特殊指令,不允许将围栏之前的常规指令与围栏之后的指令重新排序。一种这样的机制是volatile
关键字。标记volatile
的变量没有被编译器/CPU 优化,将始终直接写入/读取到主内存/从主内存读取。简而言之,volatile
确保跨 CPU 内核的变量值的可见性。
Visibility is important, but should not be confused with atomicity. Two threads incrementing the same shared variable may produce inconsistent results even though the variable is declared volatile
. This is due to the fact that on some systems the increment is actually translated into a sequence of assembler instructions that can be interrupted at any point. For such cases, critical sections such as the synchronized
keyword need to be used. This means that only a single thread can access the code enclosed in the synchronized
block. Other common uses of critical sections are atomic updates to a shared collection, when usually iterating over a collection while another thread is adding/removing items will cause an exception to be thrown.
可见性很重要,但不应与原子性混淆。即使声明了变量,两个线程递增同一个共享变量也可能产生不一致的结果volatile
。这是因为在某些系统上,增量实际上被翻译成可以在任何时候中断的汇编指令序列。对于这种情况,synchronized
需要使用关键字等关键部分。这意味着只有一个线程可以访问包含在synchronized
块中的代码。临界区的其他常见用途是对共享集合的原子更新,通常在另一个线程添加/删除项目时迭代集合时会导致抛出异常。
Finally two interesting points:
最后两个有趣的点:
synchronized
and a few other constructs such asThread.join
will introduce memory fences implicitly. Hence, incrementing a variable inside asynchronized
block does not require the variable to also bevolatile
, assuming that's the only place it's being read/written.- For simple updates such as value swap, increment, decrement, you can use non-blocking atomic methods like the ones found in
AtomicInteger
,AtomicLong
, etc. These are much faster thansynchronized
because they do not trigger a context switch in case the lock is already taken by another thread. They also introduce memory fences when used.
synchronized
以及一些其他结构,例如Thread.join
将隐式引入内存栅栏。因此,在synchronized
块内增加一个变量并不要求该变量也是volatile
,假设这是它被读/写的唯一地方。- 对于简单的更新,如价值交换,递增,递减,你可以使用非阻塞原子的方法使用的项目发现
AtomicInteger
,AtomicLong
等等,这些都是比快得多synchronized
,因为他们没有触发的情况下,锁已经采取的上下文切换另一个线程。它们在使用时还会引入内存栅栏。
回答by Roy
volatile
solves “visibility” problem across CPU cores. Therefore, value from local registers is flushed and synced with RAM. However, if we need consistent value and atomic op, we need a mechanism to defend the critical data. That can be achieved by either synchronized
block or explicit lock.
volatile
解决了跨 CPU 内核的“可见性”问题。因此,来自本地寄存器的值被刷新并与 RAM 同步。但是,如果我们需要一致的值和原子操作,我们需要一种机制来保护关键数据。这可以通过synchronized
块或显式锁来实现。