When to use volatile and synchronized

25,437

Solution 1

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:

  • updates to non-volatile long or a double may not be atomic, and
  • Java operators like ++ 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!

Solution 2

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.

Solution 3

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:

  • 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.

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.
  • 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 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.

Solution 4

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.

Solution 5

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.

Share:
25,437
Stripies
Author by

Stripies

Updated on September 29, 2020

Comments

  • Stripies
    Stripies over 3 years

    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.