Reputation: 1267
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:
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:
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.
Upvotes: 32
Views: 27480
Reputation: 48280
The synchronized
keyword
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.
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.
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
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.
If degreesCelsius
is not declared to be volatile
, the compiler is free to optimize this:
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.
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.
Upvotes: 23
Reputation: 718698
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.
If in doubt, use synchronized
. Correctness is more important than performance.
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:
long
or a double
may not be atomic, and ++
and +=
are not atomic.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 usually mean 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!
Upvotes: 47
Reputation: 569
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.
Upvotes: 1
Reputation: 62439
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
.
So, here's my general advice:
During the cycle of writing-compiling-running a program, there are two optimization points:
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.
To combat this kind of situation, memory fences were 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.
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.
Finally two interesting points:
synchronized
and a few other constructs such as Thread.join
will introduce memory fences implicitly. Hence, incrementing a variable inside a synchronized
block does not require the variable to also be volatile
, assuming that's the only place it's being read/written.AtomicInteger
, AtomicLong
, etc. These are much faster than synchronized
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.Upvotes: 12
Reputation: 116246
Note: In your first example, the field serverSocket
is actually never initialized in the code you show.
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.
In the second example, int
variables can be atomically updated so volatile
may suffice.
Upvotes: 1