Do you really understand and know how to use volatile, synchronized, and final thread communication? If you can’t answer these questions, you don’t really understand:

  1. Are operations on volatile variables necessarily atomic?

  2. What does synchronized say about locking?

  3. What exactly does final define as invariant?

Java memory model

The memory model

Before we look at the Java memory model, what is a memory model?

In multiprocessor systems, processors typically have multiple levels of caches, which can improve the speed at which the processor retrieves data and reduce the footprint on the shared memory data bus because these caches are closer to the processor and can store a portion of the data. While caching can greatly improve performance, it also presents many challenges. For example, what happens when two processors operate on the same memory address? Under what conditions can the two processors see the same value?

For a processor, a memory model defines specifications that are sufficient and necessary to make writes to memory by other processors visible to the current processor, or writes by the current processor to other processors.

A write to memory by another processor must occur before a read by the current processor to the same memory, and is said to be visible to the current processor.Copy the code

Java memory model

Knowing the memory model, you should have a better understanding of the Java memory model.

Simply put, the Java Memory Model refers to a set of specifications, the latest of which is JSR-133. This specification includes:

  1. How threads communicate with each other through memory;

  2. What is the legal way for threads to communicate with each other in order to get the desired result?

Memory structures in the Java memory model

Now that we know that the Java memory model is a set of specifications, what is the specified memory structure in this specification?

In simple terms, the Java memory model divides memory into shared memory and local memory. Shared memory, also known as heap memory, is the memory shared between threads and contains all instance fields, static fields, and array elements. Each thread has a private memory visible only to itself, called local memory.

The memory structure in the Java memory model is shown below:

Shared variables in shared memory are shared by all threads, but for efficiency, threads do not use these variables directly. Each thread stores a copy of shared memory in its own local memory and uses this copy to participate in calculations. Due to the participation of this replica, there are visibility issues between threads reading and writing to shared memory.

In order to facilitate communication between threads, Java provides three keywords: volatile, synchronized, and final. Let’s look at how to use them to communicate between threads

volatile

The variable defined by volatile, with the special properties that:

A thread’s write to a volatile variable must be visible to the thread that reads it later.

Is equivalent to

A thread that reads a volatile variable must see the last thread that wrote it before it.

To implement these semantics, Java dictates that (1) when a thread uses a volatile variable in shared memory, such as variable A in the figure, it reads directly from main memory rather than using its own copy in local memory. (2) When a thread writes to a volatile variable, it flusher the value of the shared variable to shared memory.

As we can see, volatile variables guarantee that writes to them by one thread will be immediately flushed to main memory and that copies of other threads will be invalidated. They do not guarantee that all operations to volatile variables will be atomic.

 public void add(){
     a++;         #1
 } 
Copy the code

Is equivalent to

 public void add() {
    temp = a;        
    temp = temp +1;  
    a = temp;         
 } 
Copy the code

Code 1 is not an atomic operation, so operations like a++ cause concurrent data problems.

Writes to volatile variables can be seen by subsequent reads from other threads, so we can use them for interthread communication. Such as

volatile int a;

public void set(int b) {
    a = b; 
}

public void get() {
    int i = a; 
}Copy the code

After thread A executes set(), thread B executes get(), which is equivalent to thread A sending A message to thread B.

synchronized

What if we had to use a++ as a compound operation for interthread communication? Java provides us with synchronized.

 public synchronized void add() {
    a++; 
 }
Copy the code

Synchronized makes

The code in its scope is mutually exclusive to different threads, and the thread flusher the value of the shared variable to shared memory when it releases the lock.

We can take advantage of this mutual exclusion for interthread communication. Look at the code below,

public synchronized void add() {
    a++; 
}

public synchronized void get() {
    int i = a; 
}Copy the code

When thread A executes add(), thread B calls get(). Due to mutual exclusion, thread B can only execute GET () after thread A finishes add(), and when thread A finishes add(), the lock is released, the value of A is flushed to the shared memory. So thread B gets the value of A after thread A updated it.

Compare volatile to synchronized

Based on the above analysis, we can see that volatile is somewhat similar to synchronized.

  1. When a thread writes to a volatile variable, Java flusher the value to shared memory. Synchronized, on the other hand, means that when a thread releases the lock, the value of the shared variable is flushed to main memory.

  2. A thread that reads a volatile variable invalidates a shared variable in local memory. With synchronized, shared variables in the current thread’s local memory are invalidated when a thread acquires the lock.

  3. Synchronized expands the scope of visible influence to include the blocks of code that synchronized acts on.

The final variable

The final keyword applies to variables, methods, and classes; we will only look at final variables.

What’s special about final variables is that,

Once a final variable is initialized, its value cannot be changed.

The value for an object or array refers to the reference address of the object or array. Therefore, after one thread defines a final variable, any other thread can get it. But one thing to note is that when the final variable is an object or an array,

  1. We can’t assign this variable to any other object or array, but we can change the field of the object or the elements of the array.

  2. Thread changes to the field of the object variable or to the element of the data do not have thread visibility.

conclusion

Sometimes, in the process of learning a technology, we are not limited to how to use it. After knowing how to use it, we should explore deeply why we can get the results we want after using it. We should not only know how, but also know why.