1. Volatile variables are visible



As can be seen from the figure:

Each thread has its own local memory space — thread stack space. When a thread executes, it reads a variable from main memory into its own local memory space, and then operates on the variable

② After operating on the variable, refresh the variable back to main memory at a certain time

public class RunThread extends Thread {

 private boolean isRunning = true;

 public boolean isRunning() {
 return isRunning;
 }

 public void setRunning(boolean isRunning) {
 this.isRunning = isRunning;
 }

 @Override
 public void run() {
 System.out.println("I'm inside the run method.");
 while (isRunning == true) {
 }
 System.out.println("Thread execution completed");
 }
}

public class Run {
 public static void main(String[] args) {
 try {
 RunThread thread = new RunThread();
 thread.start();
 Thread.sleep(1000);
 thread.setRunning(false); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

At line 28, the main thread wants the while loop in runThread. Java to end by setting the shared variable in the starting thread RunThread to false.

If we execute the program with the JVM-server parameter, the RunThread thread does not terminate! Hence the endless loop!!

Cause analysis:

There are now two threads, the main thread and the RunThread. They both attempt to modify the isRunning variable in line 3. According to the JVM memory model, isRunning is read by the main thread into the local thread memory space, modified, and flushed back to main memory.

While the JVM is set to run the program in -server mode, the thread keeps reading the isRunning variable from the private stack. Therefore, the RunThread thread cannot read the isRunning variable changed by the main thread

This creates an infinite loop, and runthreads cannot be terminated. This situation, in Effective JAVA, is called “active failure.”

The solution is to use the volatile keyword on the third line. Here, it forces the thread to fetch volatile variables from main memory.

2. Volatile disables instruction reordering

By atomicity, a set of steps is performed either at all or at all.

For example, the increment operation i++ has three steps:

Read the value of variable I from memory

② Increase the value of I by 1

③ Write the incremented value back to memory

This shows that i++ is not an atomic operation. Because it is divided into three steps, it is possible for a thread to interrupt when it reaches step 2, which means that only two of the steps have been executed, but not all of them.

Here’s an example of the nonatomicity of volatile:

public class MyThread extends Thread {
 public volatile static int count;

 private static void addCount() {
 for (int i = 0; i < 100; i++) {
 count++;
 }
 System.out.println("count=" + count);
 }

 @Override
 public void run() {
 addCount();
 }
}

public class Run {
 public static void main(String[] args) {
 MyThread[] mythreadArray = new MyThread[100];
 for (int i = 0; i < 100; i++) {
 mythreadArray[i] = new MyThread();
 }

 for(int i = 0; i < 100; i++) { mythreadArray[i].start(); }}}Copy the code

In line 2 of the MyThread class, the count variable is volatile

The run.java line 20 for loop creates 100 threads, and line 25 starts the 100 threads to execute addCount() 100 times plus one each

The correct expected result would be 100*100=10000, but the count doesn’t actually reach 10000

The reason is that a volatile variable does not guarantee that the operation (increment) on it will be atomic. (For increment operations, you can use JAVA’s atomic class AutoicInteger to ensure atomic increment.)

For example, if I increments to 5, thread A reads I from main memory with A value of 5, stores it in its own thread space, and increments it with A value of 6. At this point, the CPU switches to thread B and reads the value of variable I from primary and secondary memory. Thread B has already read I from main memory before thread A can write the increment back to main memory. Therefore, thread B still reads the variable I value of 5

Thread B is reading stale data, making it unsafe. This situation is called a “security failure” in Effective JAVA

In summary, volatile alone does not guarantee thread safety. (Atomicity)

3. synchronized

Synchronized can act on a piece of code or method to ensure both visibility and atomicity.

Visibility is reflected in the fact that synchronized or Lock ensures that only one thread at a time acquires the Lock and executes the synchronization code, and changes to variables are flushed to main memory before the Lock is released.

Atomicity is either not executed or executed at all.

If you have any questions about the results of Synchronized, don’t worry. Let’s take a look at the principle of Synchronized, and then go back to the above problems. Let’s start by decompiling the following code to see how Synchronized implements block synchronization:

package com.paddx.test.concurrent;

public class SynchronizedDemo {
 public void method() {
 synchronized (this) {
 System.out.println("Method 1 start"); }}}Copy the code

Decompilation result:

For the purpose of these two instructions, we refer directly to the JVM specification:

Monitorenter:

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor is associated with objectref is zero, The thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, • If another thread already owns the monitor associated with objectref, The thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

The passage roughly means:

Each object has a monitor lock. The monitor is locked when it is occupied, and the thread attempts to acquire ownership of the Monitor when it executes the Monitorenter instruction as follows:

1. If the number of entries to monitor is 0, the thread enters monitor and sets the number of entries to 1. The thread is the owner of Monitor.

2. If the thread already owns the monitor and just re-enters, the number of entries into the monitor is increased by 1.

3. If the monitor is occupied by another thread, the thread blocks until the number of monitor entries is 0, and then tries again to acquire ownership of the monitor.

Monitorexit:

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.

The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

The passage roughly means:

The thread executing monitorexit must be the owner of the monitor to which objectref corresponds.

When the instruction is executed, the number of monitor entries decreases by 1. If the number of monitor entries decreases by 1, the thread exits the monitor and is no longer the owner of the monitor. Other threads blocked by the monitor can try to take ownership of the monitor.

Synchronized semantics are implemented through a monitor object. In fact, wait/notify and other methods also rely on monitor objects. This is why only in the synchronized block or method calls to wait/notify method, otherwise will be thrown. Java lang. The cause of the exception IllegalMonitorStateException.

Synchronized is the most commonly used thread-safe method in Java concurrent programming and is relatively simple to use. However, an in-depth understanding of its principle and basic knowledge such as monitor lock can help us correctly use the Synchronized keyword on the one hand and better understand the concurrency programming mechanism on the other hand, helping us to choose a better concurrency strategy to complete tasks in different situations. On the usual encounter with a variety of concurrent problems, but also calmly deal with.

conclusion

1. Volatile can only be used at the variable level; Synchronized can be used at the variable, method, and class levels

2. Volatile only enables change visibility of variables, and does not guarantee atomicity; Synchronized can guarantee the change visibility and atomicity of variables

3. Volatile does not block threads; Synchronized can cause threads to block.

4. Volatile variables are not optimized by the compiler; Variables of the synchronized tag can be optimized by the compiler