From the school to A factory all the way sunshine vicissitudes of life

Please go to www.codercc.com

1. Brief introduction of three major properties

When analyzing thread-safety issues in concurrent programming, you often need a starting point, which is the two core: the JMM abstract memory model and the happens-before rule (already covered in this article), with three properties: atomicity, orderliness, and visibility. Having already discussed synchronized and volatile, we want to compare the atomicity, orderliness, and visibility of the two great weapons of concurrent programming.

2. The atomicity

Atomicity refers to the fact that an operation is uninterruptible, and either all of it succeeds or all of it fails, with a sense of “live or die”. Even when multiple threads are working together, an operation can be started without interference from other threads. Let’s start by looking at which atomic operations are and which are not atomic operations to get an idea:

int a = 10; / / 1

a++; / / 2

int b=a; / / 3

a = a+1; / / 4

Only the first of the four statements above is an atomic operation, assigning 10 to variable A in the thread’s working memory, whereas statement 2 (a++) actually contains three operations: 1. Read the value of variable A; 2: add one to a; 3. Assign the calculated value to variable A, but these three operations cannot constitute atomic operations. Similarly, the analysis of statements 3 and 4 shows that these two statements do not possess atomicity. Of course, the Java memory model defines eight operations as atomic and non-divisible.

  1. Lock: Acts on variables in main memory to identify a variable as a thread-exclusive state;
  2. Unlock: Applies to a variable in main memory. It releases a locked variable so that it can be locked by another thread
  3. Read: a variable acting on main memory that transfers the value of a variable from main memory to the thread’s working memory for later load action;
  4. Load: Acts on a variable in working memory. It puts the value of the variable from main memory into a copy of the variable in working memory
  5. Use: applied to a variable in working memory, which passes the value of a variable in working memory to the execution engine. This operation is performed whenever the virtual machine reaches a bytecode instruction that needs to use the value of the variable.
  6. Assign: applies to a variable in working memory. It assigns a value received from the execution engine to a variable in working memory. This operation is performed whenever the virtual machine accesses a bytecode instruction that assigns a value to the variable.
  7. Store: a variable applied to working memory that passes the value of a variable in working memory to main memory for subsequent write operations;
  8. Write (operation) : variable acting on main memory, which puts the value of the variable obtained from the working memory by the store operation into the main memory variable.

The above instructions are fairly basic and can be used as an extension of your knowledge. So how do we understand these instructions? For example, copying a variable from main memory to working memory requires read and load operations, and synchronizing working memory to main memory requires store and write operations. Note that the Java memory model only requires that these two operations be performed sequentially, not sequentially. That is, other instructions can be inserted between read and load, and other instructions can be inserted between store and writer. For example, when accessing a and B in main memory, the sequence of operations is read A,read B,load B,load A.

By atomic variables read operation, the load, the use, assign, store, write, the basic data types can be broadly think access to read and write with atomic (exception is long and double the atomic agreements)

synchronized

There are eight atomic operations, six of which are atomic for basic data types, and two atomic operations, lock and unlock. Lock and UNLOCK atomic operations can be used if we need larger atomic operations. Although the JVM does not open lock and unlock for us to use, the JVM does open it up to use the higher-level monitorenter and Monitorexit directives, reflected in Java code as the synchronized keyword, That is, synchronized satisfies atomicity.

Volatile Let’s start with an example like this:

public class VolatileExample { private static volatile int counter = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) counter++; }}); thread.start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(counter); }}Copy the code

Start 10 threads, and each thread adds 10000 times. If there is no thread safety problem, the final result should be: 10*10000 = 100000; The problem is that volatile does not guarantee atomicity. As mentioned earlier, counter++ is not an atomic operation and consists of three steps: 1. Read the value of the variable counter; 2. Add one to counter; 3. Assign the new value to the variable counter. If thread A reads counter into working memory and other threads have incremented the value, then thread A’s value is automatically an expired value, so the total result must be less than 100,000.

If volatile guarantees atomicity, the following two rules must be met:

  1. The result does not depend on the current value of the variable, or can ensure that only one thread changes the value of the variable;
  2. Variables do not need to participate in invariant constraints with other state variables

3. The order

synchronized

Synchronized semantics indicates that a lock can be acquired by only one thread at a time. When the lock is occupied, other threads can only wait. Therefore, synchronized semantics require threads to perform only “serially” when accessing and reading shared variables, and thus synchronized is ordered.

volatile

As stated in the Java memory model, the compiler and processor perform instruction reordering for performance optimization; In other words, the natural orderlessness of Java programs can be summarized as follows: if you look inside the thread, all operations are ordered; If you observe another thread from one thread, all operations are unordered. In the implementation of singleton mode, there is a double-checked Locking method. The code is as follows:

public class Singleton { private Singleton() { } private volatile static Singleton instance; public Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class){ if(instance==null){ instance = new Singleton(); } } } return instance; }}Copy the code

Why volatile here? Let’s first examine the case without volatile. The problematic statement is this:

instance = new Singleton();

This statement actually contains three operations: 1. Allocate the memory space of the object; 2. Initialize the object. 3. Set instance to the newly allocated memory address. However, due to the reordering problem, there may be the following execution order:

If 2 and 3 are reordered, thread B will evaluate if(instance==null) as true, and in fact the instance did not initialize successfully, so it will be obvious to thread B that subsequent operations will be wrong. Volatile prevents this by disallowing reordering of operations 2 and 3. Volatile contains the semantics that prohibit instruction reordering and is ordered.

4. The visibility

Visibility means that when one thread makes a change to a shared variable, other threads are immediately aware of the change. In synchronzed memory semantics, when a thread acquises a lock, it retrieves the latest value of a shared variable from main memory and synchronizes the shared variable to main memory when the lock is released. Thus, synchronized is visible. Similarly, in volatile analysis, memory visibility is achieved by adding the LOCK instruction to the instruction. Therefore, volatile is visible

5. To summarize

Through this article, mainly compared synchronized and volatile in three properties: atomicity, visibility, and order, summarized as follows:

Synchronized: atomicity, orderliness and visibility; Volatile: Orderliness and visibility

reference

The Art of Concurrent Programming in Java

In-depth Understanding of the Java Virtual Machine